Merge lib/netconfig from release.go.x.ref into release.go.x.lib.
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..53191fd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/.v23
diff --git a/cmdline/cmdline.go b/cmdline/cmdline.go
new file mode 100644
index 0000000..d10c76a
--- /dev/null
+++ b/cmdline/cmdline.go
@@ -0,0 +1,526 @@
+// Package cmdline provides a data-driven framework to simplify writing
+// command-line programs.  It includes built-in support for formatted help.
+//
+// Commands may be linked together to form a command tree.  Since commands may
+// be arbitrarily nested within other commands, it's easy to create wrapper
+// programs that invoke existing commands.
+//
+// The syntax for each command-line program is:
+//
+//   command [flags] [subcommand [flags]]* [args]
+//
+// Each sequence of flags on the command-line is associated with the command
+// that immediately precedes them.  Global flags registered with the standard
+// flags package are allowed anywhere a command-specific flag is allowed.
+package cmdline
+
+import (
+	"flag"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"strconv"
+	"strings"
+
+	"v.io/x/lib/textutil"
+)
+
+// ErrExitCode may be returned by the Run function of a Command to cause the
+// program to exit with a specific error code.
+type ErrExitCode int
+
+func (x ErrExitCode) Error() string {
+	return fmt.Sprintf("exit code %d", x)
+}
+
+// ErrUsage is returned to indicate an error in command usage; e.g. unknown
+// flags, subcommands or args.  It corresponds to exit code 1.
+const ErrUsage = ErrExitCode(1)
+
+// Command represents a single command in a command-line program.  A program
+// with subcommands is represented as a root Command with children representing
+// each subcommand.  The command graph must be a tree; each command may either
+// have exactly one parent (a sub-command), or no parent (the root), and cycles
+// are not allowed.  This makes it easier to display the usage for subcommands.
+type Command struct {
+	Name  string // Name of the command.
+	Short string // Short description, shown in help called on parent.
+	Long  string // Long description, shown in help called on itself.
+
+	// WARNING: If this Command is the root of the command tree, specifying
+	// flags this way will interfere with attempts to parse flag args in
+	// other code that may be linked in (since the other code's flag set --
+	// typically, the global flag set -- will likely not contain the flags
+	// defined here).  In such cases, it's recommended to just use the
+	// global flag set for all flags and avoid defining flags on the root
+	// command.
+	Flags    flag.FlagSet // Flags for the command.
+	ArgsName string       // Name of the args, shown in usage line.
+	ArgsLong string       // Long description of the args, shown in help.
+
+	// Children of the command.  The framework will match args[0] against each
+	// child's name, and call Run on the first matching child.
+	Children []*Command
+
+	// Topics that provide additional info via the default help command.
+	Topics []Topic
+
+	// Run is a function that runs cmd with args.  If both Children and Run are
+	// specified, Run will only be called if none of the children match.  It is an
+	// error if neither is specified.  The special ErrExitCode error may be
+	// returned to indicate the command should exit with a specific exit code.
+	Run func(cmd *Command, args []string) error
+
+	// parent holds the parent of this Command, or nil if this is the root.
+	parent *Command
+
+	// stdout and stderr are set through Init.
+	stdout, stderr io.Writer
+
+	// parseFlags holds the merged flags used for parsing.  Each command starts
+	// with its own Flags, and we merge in all global flags.  If the same flag is
+	// specified in both sets, the command's own flag wins.
+	parseFlags *flag.FlagSet
+
+	// isDefaultHelp indicates whether this is the the default help command
+	// provided by the framework.
+	isDefaultHelp bool
+
+	// TODO(toddw): If necessary we can add alias support, e.g. for abbreviations.
+	//   Alias map[string]string
+}
+
+// Topic represents an additional help topic that is accessed via the default
+// help command.
+type Topic struct {
+	Name  string // Name of the topic.
+	Short string // Short description, shown in help for the command.
+	Long  string // Long description, shown in help for this topic.
+}
+
+// style describes the formatting style for usage descriptions.
+type style int
+
+const (
+	styleText  style = iota // Default style, good for cmdline output.
+	styleGoDoc              // Style good for godoc processing.
+)
+
+// String returns the human-readable representation of the style.
+func (s *style) String() string {
+	switch *s {
+	case styleText:
+		return "text"
+	case styleGoDoc:
+		return "godoc"
+	default:
+		panic(fmt.Errorf("Unhandled style %d", *s))
+	}
+}
+
+// Set implements the flag.Value interface method.
+func (s *style) Set(value string) error {
+	switch value {
+	case "text":
+		*s = styleText
+	case "godoc":
+		*s = styleGoDoc
+	default:
+		return fmt.Errorf("Unknown style %q", value)
+	}
+	return nil
+}
+
+// Stdout is where output goes.  Typically os.Stdout.
+func (cmd *Command) Stdout() io.Writer {
+	return cmd.stdout
+}
+
+// Stderr is where error messages go.  Typically os.Stderr
+func (cmd *Command) Stderr() io.Writer {
+	return cmd.stderr
+}
+
+// UsageErrorf prints the error message represented by the printf-style format
+// string and args, followed by the usage description of cmd.  Returns ErrUsage
+// to make it easy to use from within the cmd.Run function.
+func (cmd *Command) UsageErrorf(format string, v ...interface{}) error {
+	fmt.Fprint(cmd.stderr, "ERROR: ")
+	fmt.Fprintf(cmd.stderr, format, v...)
+	fmt.Fprint(cmd.stderr, "\n\n")
+	cmd.writeUsage(cmd.stderr)
+	return ErrUsage
+}
+
+// Have a reasonable default for the output width in runes.
+const defaultWidth = 80
+
+func outputWidth() int {
+	if width, err := strconv.Atoi(os.Getenv("CMDLINE_WIDTH")); err == nil && width != 0 {
+		return width
+	}
+	if _, width, err := textutil.TerminalSize(); err == nil && width != 0 {
+		return width
+	}
+	return defaultWidth
+}
+
+func (cmd *Command) writeUsage(w io.Writer) {
+	lineWriter := textutil.NewUTF8LineWriter(w, outputWidth())
+	cmd.usage(lineWriter, true)
+	lineWriter.Flush()
+}
+
+// usage prints the usage of cmd to the writer.  The firstCall boolean is set to
+// false when printing usage for multiple commands, and is used to avoid
+// printing redundant information (e.g. help command, global flags).
+func (cmd *Command) usage(w *textutil.LineWriter, firstCall bool) {
+	fmt.Fprintln(w, cmd.Long)
+	fmt.Fprintln(w)
+	// Usage line.
+	hasFlags := numFlags(&cmd.Flags) > 0
+	fmt.Fprintln(w, "Usage:")
+	path := cmd.namePath()
+	pathf := "   " + path
+	if hasFlags {
+		pathf += " [flags]"
+	}
+	if len(cmd.Children) > 0 {
+		fmt.Fprintln(w, pathf, "<command>")
+	}
+	if cmd.Run != nil {
+		if cmd.ArgsName != "" {
+			fmt.Fprintln(w, pathf, cmd.ArgsName)
+		} else {
+			fmt.Fprintln(w, pathf)
+		}
+	}
+	if len(cmd.Children) == 0 && cmd.Run == nil {
+		// This is a specification error.
+		fmt.Fprintln(w, pathf, "[ERROR: neither Children nor Run is specified]")
+	}
+	// Commands.
+	const minNameWidth = 11
+	if len(cmd.Children) > 0 {
+		fmt.Fprintln(w)
+		fmt.Fprintln(w, "The", path, "commands are:")
+		nameWidth := minNameWidth
+		for _, child := range cmd.Children {
+			if len(child.Name) > nameWidth {
+				nameWidth = len(child.Name)
+			}
+		}
+		// Print as a table with aligned columns Name and Short.
+		w.SetIndents(spaces(3), spaces(3+nameWidth+1))
+		for _, child := range cmd.Children {
+			// Don't repeatedly list default help command.
+			if !child.isDefaultHelp || firstCall {
+				fmt.Fprintf(w, "%-[1]*[2]s %[3]s", nameWidth, child.Name, child.Short)
+				w.Flush()
+			}
+		}
+		w.SetIndents()
+		if firstCall {
+			fmt.Fprintf(w, "Run \"%s help [command]\" for command usage.\n", path)
+		}
+	}
+	// Args.
+	if cmd.Run != nil && cmd.ArgsLong != "" {
+		fmt.Fprintln(w)
+		fmt.Fprintln(w, cmd.ArgsLong)
+	}
+	// Help topics.
+	if len(cmd.Topics) > 0 {
+		fmt.Fprintln(w)
+		fmt.Fprintln(w, "The", path, "additional help topics are:")
+		nameWidth := minNameWidth
+		for _, topic := range cmd.Topics {
+			if len(topic.Name) > nameWidth {
+				nameWidth = len(topic.Name)
+			}
+		}
+		// Print as a table with aligned columns Name and Short.
+		w.SetIndents(spaces(3), spaces(3+nameWidth+1))
+		for _, topic := range cmd.Topics {
+			fmt.Fprintf(w, "%-[1]*[2]s %[3]s", nameWidth, topic.Name, topic.Short)
+			w.Flush()
+		}
+		w.SetIndents()
+		if firstCall {
+			fmt.Fprintf(w, "Run \"%s help [topic]\" for topic details.\n", path)
+		}
+	}
+	// Flags.
+	if hasFlags {
+		fmt.Fprintln(w)
+		fmt.Fprintln(w, "The", path, "flags are:")
+		printFlags(w, &cmd.Flags)
+	}
+	// Global flags.
+	if numFlags(flag.CommandLine) > 0 && firstCall {
+		fmt.Fprintln(w)
+		fmt.Fprintln(w, "The global flags are:")
+		printFlags(w, flag.CommandLine)
+	}
+}
+
+// namePath returns the path of command names up to cmd.
+func (cmd *Command) namePath() string {
+	var path []string
+	for ; cmd != nil; cmd = cmd.parent {
+		path = append([]string{cmd.Name}, path...)
+	}
+	return strings.Join(path, " ")
+}
+
+func numFlags(set *flag.FlagSet) (num int) {
+	set.VisitAll(func(*flag.Flag) {
+		num++
+	})
+	return
+}
+
+func printFlags(w *textutil.LineWriter, set *flag.FlagSet) {
+	set.VisitAll(func(f *flag.Flag) {
+		fmt.Fprintf(w, " -%s=%s", f.Name, f.DefValue)
+		w.SetIndents(spaces(3))
+		fmt.Fprintln(w, f.Usage)
+		w.SetIndents()
+	})
+}
+
+func spaces(count int) string {
+	return strings.Repeat(" ", count)
+}
+
+// newDefaultHelp creates a new default help command.  We need to create new
+// instances since the parent for each help command is different.
+func newDefaultHelp() *Command {
+	helpStyle := styleText
+	help := &Command{
+		Name:  helpName,
+		Short: "Display help for commands or topics",
+		Long: `
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+The output is formatted to a target width in runes.  The target width is
+determined by checking the environment variable CMDLINE_WIDTH, falling back on
+the terminal width from the OS, falling back on 80 chars.  By setting
+CMDLINE_WIDTH=x, if x > 0 the width is x, if x < 0 the width is unlimited, and
+if x == 0 or is unset one of the fallbacks is used.
+`,
+		ArgsName: "[command/topic ...]",
+		ArgsLong: `
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+`,
+		Run: func(cmd *Command, args []string) error {
+			// Help applies to its parent - e.g. "foo help" applies to the foo command.
+			lineWriter := textutil.NewUTF8LineWriter(cmd.stdout, outputWidth())
+			defer lineWriter.Flush()
+			return runHelp(lineWriter, cmd.parent, args, helpStyle)
+		},
+		isDefaultHelp: true,
+	}
+	help.Flags.Var(&helpStyle, "style", `The formatting style for help output, either "text" or "godoc".`)
+	return help
+}
+
+const helpName = "help"
+
+// runHelp runs the "help" command.
+func runHelp(w *textutil.LineWriter, cmd *Command, args []string, style style) error {
+	if len(args) == 0 {
+		cmd.usage(w, true)
+		return nil
+	}
+	if args[0] == "..." {
+		recursiveHelp(w, cmd, style, true)
+		return nil
+	}
+	// Try to display help for the subcommand.
+	subName, subArgs := args[0], args[1:]
+	for _, child := range cmd.Children {
+		if child.Name == subName {
+			return runHelp(w, child, subArgs, style)
+		}
+	}
+	// Try to display help for the help topic.
+	for _, topic := range cmd.Topics {
+		if topic.Name == subName {
+			fmt.Fprintln(w, topic.Long)
+			return nil
+		}
+	}
+	return cmd.UsageErrorf("%s: unknown command or topic %q", cmd.namePath(), subName)
+}
+
+// recursiveHelp prints help recursively via DFS from this cmd onward.
+func recursiveHelp(w *textutil.LineWriter, cmd *Command, style style, firstCall bool) {
+	if !firstCall {
+		lineBreak(w, style)
+		// Title-case required for godoc to recognize this as a section header.
+		header := strings.Title(cmd.namePath())
+		fmt.Fprintln(w, header)
+		fmt.Fprintln(w)
+	}
+	cmd.usage(w, firstCall)
+	for _, child := range cmd.Children {
+		// Don't repeatedly print default help command.
+		if !child.isDefaultHelp || firstCall {
+			recursiveHelp(w, child, style, false)
+		}
+	}
+	for _, topic := range cmd.Topics {
+		lineBreak(w, style)
+		// Title-case required for godoc to recognize this as a section header.
+		header := strings.Title(cmd.namePath()+" "+topic.Name) + " - help topic"
+		fmt.Fprintln(w, header)
+		fmt.Fprintln(w)
+		fmt.Fprintln(w, topic.Long)
+	}
+}
+
+func lineBreak(w *textutil.LineWriter, style style) {
+	w.Flush()
+	switch style {
+	case styleText:
+		width := w.Width()
+		if width < 0 {
+			// If the user has chosen an "unlimited" word-wrapping width, we still
+			// need a reasonable width for our visual line break.
+			width = defaultWidth
+		}
+		fmt.Fprintln(w, strings.Repeat("=", width))
+	case styleGoDoc:
+		fmt.Fprintln(w)
+	}
+	w.Flush()
+}
+
+func trimNewlines(s *string) { *s = strings.Trim(*s, "\n") }
+
+// Init initializes all nodes in the command tree rooted at cmd.  Init must be
+// called before Execute.
+func (cmd *Command) Init(parent *Command, stdout, stderr io.Writer) {
+	cmd.parent = parent
+	cmd.stdout = stdout
+	cmd.stderr = stderr
+	trimNewlines(&cmd.Short)
+	trimNewlines(&cmd.Long)
+	trimNewlines(&cmd.ArgsLong)
+	for tx := range cmd.Topics {
+		trimNewlines(&cmd.Topics[tx].Short)
+		trimNewlines(&cmd.Topics[tx].Long)
+	}
+	// Add help command, if it doesn't already exist.
+	hasHelp := false
+	for _, child := range cmd.Children {
+		if child.Name == helpName {
+			hasHelp = true
+			break
+		}
+	}
+	if !hasHelp && cmd.Name != helpName && len(cmd.Children) > 0 {
+		cmd.Children = append(cmd.Children, newDefaultHelp())
+	}
+	// Merge command-specific and global flags into parseFlags.  We want to handle
+	// all error output ourselves, so we:
+	//   1) Set flag.ContinueOnError so that Parse() doesn't exit or panic.
+	//   2) Discard all output (can't be nil, that means stderr).
+	//   3) Set an empty Usage function (can't be nil, that means default).
+	cmd.parseFlags = flag.NewFlagSet(cmd.Name, flag.ContinueOnError)
+	cmd.parseFlags.SetOutput(ioutil.Discard)
+	cmd.parseFlags.Usage = emptyUsage
+	mergeFlags(cmd.parseFlags, &cmd.Flags)
+	mergeFlags(cmd.parseFlags, flag.CommandLine)
+	// Call children recursively.
+	for _, child := range cmd.Children {
+		child.Init(cmd, stdout, stderr)
+	}
+}
+
+func mergeFlags(dst, src *flag.FlagSet) {
+	src.VisitAll(func(f *flag.Flag) {
+		trimNewlines(&f.Usage)
+		if dst.Lookup(f.Name) == nil {
+			dst.Var(f.Value, f.Name, f.Usage)
+		}
+	})
+}
+
+func emptyUsage() {}
+
+// Execute the command with the given args.  The returned error is ErrUsage if
+// there are usage errors, otherwise it is whatever the leaf command returns
+// from its Run function.
+func (cmd *Command) Execute(args []string) error {
+	path := cmd.namePath()
+	// Parse the merged flags.
+	if err := cmd.parseFlags.Parse(args); err != nil {
+		if err == flag.ErrHelp {
+			cmd.writeUsage(cmd.stdout)
+			return nil
+		}
+		return cmd.UsageErrorf("%s: %v", path, err)
+	}
+	args = cmd.parseFlags.Args()
+	// Look for matching children.
+	if len(args) > 0 {
+		subName, subArgs := args[0], args[1:]
+		for _, child := range cmd.Children {
+			if child.Name == subName {
+				return child.Execute(subArgs)
+			}
+		}
+	}
+	// No matching children, try Run.
+	if cmd.Run != nil {
+		if cmd.ArgsName == "" && len(args) > 0 {
+			if len(cmd.Children) > 0 {
+				return cmd.UsageErrorf("%s: unknown command %q", path, args[0])
+			}
+			return cmd.UsageErrorf("%s doesn't take any arguments", path)
+		}
+		return cmd.Run(cmd, args)
+	}
+	switch {
+	case len(cmd.Children) == 0:
+		return cmd.UsageErrorf("%s: neither Children nor Run is specified", path)
+	case len(args) > 0:
+		return cmd.UsageErrorf("%s: unknown command %q", path, args[0])
+	default:
+		return cmd.UsageErrorf("%s: no command specified", path)
+	}
+}
+
+// Main executes the command tree rooted at cmd, writing output to os.Stdout,
+// writing errors to os.Stderr, and getting args from os.Args.  We return
+// an appropriate exit code depending on whether there were errors or not.
+// Users should call os.Exit(exitCode).
+//
+// Many main packages can use this simple pattern:
+//
+// var cmd := &cmdline.Command{
+//   ...
+// }
+//
+// func main() {
+//   os.Exit(cmd.Main())
+// }
+//
+func (cmd *Command) Main() (exitCode int) {
+	cmd.Init(nil, os.Stdout, os.Stderr)
+	if err := cmd.Execute(os.Args[1:]); err != nil {
+		if code, ok := err.(ErrExitCode); ok {
+			return int(code)
+		}
+		fmt.Fprintln(os.Stderr, "ERROR:", err)
+		return 2
+	}
+	return 0
+}
diff --git a/cmdline/cmdline_test.go b/cmdline/cmdline_test.go
new file mode 100644
index 0000000..6cf6b0e
--- /dev/null
+++ b/cmdline/cmdline_test.go
@@ -0,0 +1,2238 @@
+package cmdline
+
+import (
+	"bytes"
+	"errors"
+	"flag"
+	"fmt"
+	"os"
+	"regexp"
+	"strings"
+	"testing"
+)
+
+var (
+	errEcho           = errors.New("echo error")
+	flagExtra         bool
+	optNoNewline      bool
+	flagTopLevelExtra bool
+	globalFlag1       string
+	globalFlag2       *int64
+)
+
+// runEcho is used to implement commands for our tests.
+func runEcho(cmd *Command, args []string) error {
+	if len(args) == 1 {
+		if args[0] == "error" {
+			return errEcho
+		} else if args[0] == "bad_arg" {
+			return cmd.UsageErrorf("Invalid argument %v", args[0])
+		}
+	}
+	if flagExtra {
+		args = append(args, "extra")
+	}
+	if flagTopLevelExtra {
+		args = append(args, "tlextra")
+	}
+	if optNoNewline {
+		fmt.Fprint(cmd.Stdout(), args)
+	} else {
+		fmt.Fprintln(cmd.Stdout(), args)
+	}
+	return nil
+}
+
+// runHello is another function for test commands.
+func runHello(cmd *Command, args []string) error {
+	if flagTopLevelExtra {
+		args = append(args, "tlextra")
+	}
+	fmt.Fprintln(cmd.Stdout(), strings.Join(append([]string{"Hello"}, args...), " "))
+	return nil
+}
+
+type testCase struct {
+	Args        []string
+	Err         error
+	Stdout      string
+	Stderr      string
+	GlobalFlag1 string
+	GlobalFlag2 int64
+}
+
+func init() {
+	os.Setenv("CMDLINE_WIDTH", "80") // make sure the formatting stays the same.
+	flag.StringVar(&globalFlag1, "global1", "", "global test flag 1")
+	globalFlag2 = flag.Int64("global2", 0, "global test flag 2")
+}
+
+func stripOutput(got string) string {
+	// The global flags include the flags from the testing package, so strip them
+	// out before the comparison.
+	re := regexp.MustCompile(" -test[^\n]+\n(?:   [^\n]+\n)+")
+	return re.ReplaceAllLiteralString(got, "")
+}
+
+func runTestCases(t *testing.T, cmd *Command, tests []testCase) {
+	for _, test := range tests {
+		// Reset global variables before running each test case.
+		var stdout bytes.Buffer
+		var stderr bytes.Buffer
+		flagExtra = false
+		flagTopLevelExtra = false
+		optNoNewline = false
+		globalFlag1 = ""
+		*globalFlag2 = 0
+
+		// Run the execute function and check against expected results.
+		cmd.Init(nil, &stdout, &stderr)
+		if err := cmd.Execute(test.Args); err != test.Err {
+			t.Errorf("Ran with args %q\n GOT error:\n%q\nWANT error:\n%q", test.Args, err, test.Err)
+		}
+		if got, want := stripOutput(stdout.String()), test.Stdout; got != want {
+			t.Errorf("Ran with args %q\n GOT stdout:\n%q\nWANT stdout:\n%q", test.Args, got, want)
+		}
+		if got, want := stripOutput(stderr.String()), test.Stderr; got != want {
+			t.Errorf("Ran with args %q\n GOT stderr:\n%q\nWANT stderr:\n%q", test.Args, got, want)
+		}
+		if got, want := globalFlag1, test.GlobalFlag1; got != want {
+			t.Errorf("global1 flag got %q, want %q", got, want)
+		}
+		if got, want := *globalFlag2, test.GlobalFlag2; got != want {
+			t.Errorf("global2 flag got %q, want %q", got, want)
+		}
+	}
+}
+
+func TestNoCommands(t *testing.T) {
+	cmd := &Command{
+		Name:  "nocmds",
+		Short: "Nocmds is invalid.",
+		Long:  "Nocmds has no commands and no run function.",
+	}
+
+	var tests = []testCase{
+		{
+			Args: []string{},
+			Err:  ErrUsage,
+			Stderr: `ERROR: nocmds: neither Children nor Run is specified
+
+Nocmds has no commands and no run function.
+
+Usage:
+   nocmds [ERROR: neither Children nor Run is specified]
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args: []string{"foo"},
+			Err:  ErrUsage,
+			Stderr: `ERROR: nocmds: neither Children nor Run is specified
+
+Nocmds has no commands and no run function.
+
+Usage:
+   nocmds [ERROR: neither Children nor Run is specified]
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+	}
+	runTestCases(t, cmd, tests)
+}
+
+func TestOneCommand(t *testing.T) {
+	cmdEcho := &Command{
+		Name:  "echo",
+		Short: "Print strings on stdout",
+		Long: `
+Echo prints any strings passed in to stdout.
+`,
+		Run:      runEcho,
+		ArgsName: "[strings]",
+		ArgsLong: "[strings] are arbitrary strings that will be echoed.",
+	}
+
+	prog := &Command{
+		Name:     "onecmd",
+		Short:    "Onecmd program.",
+		Long:     "Onecmd only has the echo command.",
+		Children: []*Command{cmdEcho},
+	}
+
+	var tests = []testCase{
+		{
+			Args: []string{},
+			Err:  ErrUsage,
+			Stderr: `ERROR: onecmd: no command specified
+
+Onecmd only has the echo command.
+
+Usage:
+   onecmd <command>
+
+The onecmd commands are:
+   echo        Print strings on stdout
+   help        Display help for commands or topics
+Run "onecmd help [command]" for command usage.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args: []string{"foo"},
+			Err:  ErrUsage,
+			Stderr: `ERROR: onecmd: unknown command "foo"
+
+Onecmd only has the echo command.
+
+Usage:
+   onecmd <command>
+
+The onecmd commands are:
+   echo        Print strings on stdout
+   help        Display help for commands or topics
+Run "onecmd help [command]" for command usage.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args: []string{"help"},
+			Stdout: `Onecmd only has the echo command.
+
+Usage:
+   onecmd <command>
+
+The onecmd commands are:
+   echo        Print strings on stdout
+   help        Display help for commands or topics
+Run "onecmd help [command]" for command usage.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args: []string{"help", "echo"},
+			Stdout: `Echo prints any strings passed in to stdout.
+
+Usage:
+   onecmd echo [strings]
+
+[strings] are arbitrary strings that will be echoed.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args: []string{"help", "help"},
+			Stdout: `Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+The output is formatted to a target width in runes.  The target width is
+determined by checking the environment variable CMDLINE_WIDTH, falling back on
+the terminal width from the OS, falling back on 80 chars.  By setting
+CMDLINE_WIDTH=x, if x > 0 the width is x, if x < 0 the width is unlimited, and
+if x == 0 or is unset one of the fallbacks is used.
+
+Usage:
+   onecmd help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The onecmd help flags are:
+ -style=text
+   The formatting style for help output, either "text" or "godoc".
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args: []string{"help", "..."},
+			Stdout: `Onecmd only has the echo command.
+
+Usage:
+   onecmd <command>
+
+The onecmd commands are:
+   echo        Print strings on stdout
+   help        Display help for commands or topics
+Run "onecmd help [command]" for command usage.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+================================================================================
+Onecmd Echo
+
+Echo prints any strings passed in to stdout.
+
+Usage:
+   onecmd echo [strings]
+
+[strings] are arbitrary strings that will be echoed.
+================================================================================
+Onecmd Help
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+The output is formatted to a target width in runes.  The target width is
+determined by checking the environment variable CMDLINE_WIDTH, falling back on
+the terminal width from the OS, falling back on 80 chars.  By setting
+CMDLINE_WIDTH=x, if x > 0 the width is x, if x < 0 the width is unlimited, and
+if x == 0 or is unset one of the fallbacks is used.
+
+Usage:
+   onecmd help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The onecmd help flags are:
+ -style=text
+   The formatting style for help output, either "text" or "godoc".
+`,
+		},
+		{
+			Args: []string{"help", "foo"},
+			Err:  ErrUsage,
+			Stderr: `ERROR: onecmd: unknown command or topic "foo"
+
+Onecmd only has the echo command.
+
+Usage:
+   onecmd <command>
+
+The onecmd commands are:
+   echo        Print strings on stdout
+   help        Display help for commands or topics
+Run "onecmd help [command]" for command usage.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args:   []string{"echo", "foo", "bar"},
+			Stdout: "[foo bar]\n",
+		},
+		{
+			Args: []string{"echo", "error"},
+			Err:  errEcho,
+		},
+		{
+			Args: []string{"echo", "bad_arg"},
+			Err:  ErrUsage,
+			Stderr: `ERROR: Invalid argument bad_arg
+
+Echo prints any strings passed in to stdout.
+
+Usage:
+   onecmd echo [strings]
+
+[strings] are arbitrary strings that will be echoed.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+	}
+	runTestCases(t, prog, tests)
+}
+
+func TestMultiCommands(t *testing.T) {
+	cmdEcho := &Command{
+		Run:   runEcho,
+		Name:  "echo",
+		Short: "Print strings on stdout",
+		Long: `
+Echo prints any strings passed in to stdout.
+`,
+		ArgsName: "[strings]",
+		ArgsLong: "[strings] are arbitrary strings that will be echoed.",
+	}
+	var cmdEchoOpt = &Command{
+		Run:   runEcho,
+		Name:  "echoopt",
+		Short: "Print strings on stdout, with opts",
+		// Try varying number of header/trailer newlines around the long description.
+		Long: `Echoopt prints any args passed in to stdout.
+
+
+`,
+		ArgsName: "[args]",
+		ArgsLong: "[args] are arbitrary strings that will be echoed.",
+	}
+	cmdEchoOpt.Flags.BoolVar(&optNoNewline, "n", false, "Do not output trailing newline")
+
+	prog := &Command{
+		Name:     "multi",
+		Short:    "Multi test command",
+		Long:     "Multi has two variants of echo.",
+		Children: []*Command{cmdEcho, cmdEchoOpt},
+	}
+	prog.Flags.BoolVar(&flagExtra, "extra", false, "Print an extra arg")
+
+	var tests = []testCase{
+		{
+			Args: []string{},
+			Err:  ErrUsage,
+			Stderr: `ERROR: multi: no command specified
+
+Multi has two variants of echo.
+
+Usage:
+   multi [flags] <command>
+
+The multi commands are:
+   echo        Print strings on stdout
+   echoopt     Print strings on stdout, with opts
+   help        Display help for commands or topics
+Run "multi help [command]" for command usage.
+
+The multi flags are:
+ -extra=false
+   Print an extra arg
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args: []string{"help"},
+			Stdout: `Multi has two variants of echo.
+
+Usage:
+   multi [flags] <command>
+
+The multi commands are:
+   echo        Print strings on stdout
+   echoopt     Print strings on stdout, with opts
+   help        Display help for commands or topics
+Run "multi help [command]" for command usage.
+
+The multi flags are:
+ -extra=false
+   Print an extra arg
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args: []string{"help", "..."},
+			Stdout: `Multi has two variants of echo.
+
+Usage:
+   multi [flags] <command>
+
+The multi commands are:
+   echo        Print strings on stdout
+   echoopt     Print strings on stdout, with opts
+   help        Display help for commands or topics
+Run "multi help [command]" for command usage.
+
+The multi flags are:
+ -extra=false
+   Print an extra arg
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+================================================================================
+Multi Echo
+
+Echo prints any strings passed in to stdout.
+
+Usage:
+   multi echo [strings]
+
+[strings] are arbitrary strings that will be echoed.
+================================================================================
+Multi Echoopt
+
+Echoopt prints any args passed in to stdout.
+
+Usage:
+   multi echoopt [flags] [args]
+
+[args] are arbitrary strings that will be echoed.
+
+The multi echoopt flags are:
+ -n=false
+   Do not output trailing newline
+================================================================================
+Multi Help
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+The output is formatted to a target width in runes.  The target width is
+determined by checking the environment variable CMDLINE_WIDTH, falling back on
+the terminal width from the OS, falling back on 80 chars.  By setting
+CMDLINE_WIDTH=x, if x > 0 the width is x, if x < 0 the width is unlimited, and
+if x == 0 or is unset one of the fallbacks is used.
+
+Usage:
+   multi help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The multi help flags are:
+ -style=text
+   The formatting style for help output, either "text" or "godoc".
+`,
+		},
+		{
+			Args: []string{"help", "echo"},
+			Stdout: `Echo prints any strings passed in to stdout.
+
+Usage:
+   multi echo [strings]
+
+[strings] are arbitrary strings that will be echoed.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args: []string{"help", "echoopt"},
+			Stdout: `Echoopt prints any args passed in to stdout.
+
+Usage:
+   multi echoopt [flags] [args]
+
+[args] are arbitrary strings that will be echoed.
+
+The multi echoopt flags are:
+ -n=false
+   Do not output trailing newline
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args: []string{"help", "foo"},
+			Err:  ErrUsage,
+			Stderr: `ERROR: multi: unknown command or topic "foo"
+
+Multi has two variants of echo.
+
+Usage:
+   multi [flags] <command>
+
+The multi commands are:
+   echo        Print strings on stdout
+   echoopt     Print strings on stdout, with opts
+   help        Display help for commands or topics
+Run "multi help [command]" for command usage.
+
+The multi flags are:
+ -extra=false
+   Print an extra arg
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args:   []string{"echo", "foo", "bar"},
+			Stdout: "[foo bar]\n",
+		},
+		{
+			Args:   []string{"-extra", "echo", "foo", "bar"},
+			Stdout: "[foo bar extra]\n",
+		},
+		{
+			Args: []string{"echo", "error"},
+			Err:  errEcho,
+		},
+		{
+			Args:   []string{"echoopt", "foo", "bar"},
+			Stdout: "[foo bar]\n",
+		},
+		{
+			Args:   []string{"-extra", "echoopt", "foo", "bar"},
+			Stdout: "[foo bar extra]\n",
+		},
+		{
+			Args:   []string{"echoopt", "-n", "foo", "bar"},
+			Stdout: "[foo bar]",
+		},
+		{
+			Args:   []string{"-extra", "echoopt", "-n", "foo", "bar"},
+			Stdout: "[foo bar extra]",
+		},
+		{
+			Args:        []string{"-global1=globalStringValue", "-extra", "echoopt", "-n", "foo", "bar"},
+			Stdout:      "[foo bar extra]",
+			GlobalFlag1: "globalStringValue",
+		},
+		{
+			Args:        []string{"-global2=42", "echoopt", "-n", "foo", "bar"},
+			Stdout:      "[foo bar]",
+			GlobalFlag2: 42,
+		},
+		{
+			Args:        []string{"-global1=globalStringOtherValue", "-global2=43", "-extra", "echoopt", "-n", "foo", "bar"},
+			Stdout:      "[foo bar extra]",
+			GlobalFlag1: "globalStringOtherValue",
+			GlobalFlag2: 43,
+		},
+		{
+			Args: []string{"echoopt", "error"},
+			Err:  errEcho,
+		},
+		{
+			Args: []string{"echo", "-n", "foo", "bar"},
+			Err:  ErrUsage,
+			Stderr: `ERROR: multi echo: flag provided but not defined: -n
+
+Echo prints any strings passed in to stdout.
+
+Usage:
+   multi echo [strings]
+
+[strings] are arbitrary strings that will be echoed.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args: []string{"-nosuchflag", "echo", "foo", "bar"},
+			Err:  ErrUsage,
+			Stderr: `ERROR: multi: flag provided but not defined: -nosuchflag
+
+Multi has two variants of echo.
+
+Usage:
+   multi [flags] <command>
+
+The multi commands are:
+   echo        Print strings on stdout
+   echoopt     Print strings on stdout, with opts
+   help        Display help for commands or topics
+Run "multi help [command]" for command usage.
+
+The multi flags are:
+ -extra=false
+   Print an extra arg
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+	}
+	runTestCases(t, prog, tests)
+}
+
+func TestMultiLevelCommands(t *testing.T) {
+	cmdEcho := &Command{
+		Run:   runEcho,
+		Name:  "echo",
+		Short: "Print strings on stdout",
+		Long: `
+Echo prints any strings passed in to stdout.
+`,
+		ArgsName: "[strings]",
+		ArgsLong: "[strings] are arbitrary strings that will be echoed.",
+	}
+	cmdEchoOpt := &Command{
+		Run:   runEcho,
+		Name:  "echoopt",
+		Short: "Print strings on stdout, with opts",
+		// Try varying number of header/trailer newlines around the long description.
+		Long: `Echoopt prints any args passed in to stdout.
+
+
+`,
+		ArgsName: "[args]",
+		ArgsLong: "[args] are arbitrary strings that will be echoed.",
+	}
+	cmdEchoOpt.Flags.BoolVar(&optNoNewline, "n", false, "Do not output trailing newline")
+	cmdHello := &Command{
+		Run:   runHello,
+		Name:  "hello",
+		Short: "Print strings on stdout preceded by \"Hello\"",
+		Long: `
+Hello prints any strings passed in to stdout preceded by "Hello".
+`,
+		ArgsName: "[strings]",
+		ArgsLong: "[strings] are arbitrary strings that will be printed.",
+	}
+	echoProg := &Command{
+		Name:     "echoprog",
+		Short:    "Set of echo commands",
+		Long:     "Echoprog has two variants of echo.",
+		Children: []*Command{cmdEcho, cmdEchoOpt},
+		Topics: []Topic{
+			{Name: "topic3", Short: "Help topic 3 short", Long: "Help topic 3 long."},
+		},
+	}
+	echoProg.Flags.BoolVar(&flagExtra, "extra", false, "Print an extra arg")
+	prog := &Command{
+		Name:     "toplevelprog",
+		Short:    "Top level prog",
+		Long:     "Toplevelprog has the echo subprogram and the hello command.",
+		Children: []*Command{echoProg, cmdHello},
+		Topics: []Topic{
+			{Name: "topic1", Short: "Help topic 1 short", Long: "Help topic 1 long."},
+			{Name: "topic2", Short: "Help topic 2 short", Long: "Help topic 2 long."},
+		},
+	}
+	prog.Flags.BoolVar(&flagTopLevelExtra, "tlextra", false, "Print an extra arg for all commands")
+
+	var tests = []testCase{
+		{
+			Args: []string{},
+			Err:  ErrUsage,
+			Stderr: `ERROR: toplevelprog: no command specified
+
+Toplevelprog has the echo subprogram and the hello command.
+
+Usage:
+   toplevelprog [flags] <command>
+
+The toplevelprog commands are:
+   echoprog    Set of echo commands
+   hello       Print strings on stdout preceded by "Hello"
+   help        Display help for commands or topics
+Run "toplevelprog help [command]" for command usage.
+
+The toplevelprog additional help topics are:
+   topic1      Help topic 1 short
+   topic2      Help topic 2 short
+Run "toplevelprog help [topic]" for topic details.
+
+The toplevelprog flags are:
+ -tlextra=false
+   Print an extra arg for all commands
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args: []string{"help"},
+			Stdout: `Toplevelprog has the echo subprogram and the hello command.
+
+Usage:
+   toplevelprog [flags] <command>
+
+The toplevelprog commands are:
+   echoprog    Set of echo commands
+   hello       Print strings on stdout preceded by "Hello"
+   help        Display help for commands or topics
+Run "toplevelprog help [command]" for command usage.
+
+The toplevelprog additional help topics are:
+   topic1      Help topic 1 short
+   topic2      Help topic 2 short
+Run "toplevelprog help [topic]" for topic details.
+
+The toplevelprog flags are:
+ -tlextra=false
+   Print an extra arg for all commands
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args: []string{"help", "..."},
+			Stdout: `Toplevelprog has the echo subprogram and the hello command.
+
+Usage:
+   toplevelprog [flags] <command>
+
+The toplevelprog commands are:
+   echoprog    Set of echo commands
+   hello       Print strings on stdout preceded by "Hello"
+   help        Display help for commands or topics
+Run "toplevelprog help [command]" for command usage.
+
+The toplevelprog additional help topics are:
+   topic1      Help topic 1 short
+   topic2      Help topic 2 short
+Run "toplevelprog help [topic]" for topic details.
+
+The toplevelprog flags are:
+ -tlextra=false
+   Print an extra arg for all commands
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+================================================================================
+Toplevelprog Echoprog
+
+Echoprog has two variants of echo.
+
+Usage:
+   toplevelprog echoprog [flags] <command>
+
+The toplevelprog echoprog commands are:
+   echo        Print strings on stdout
+   echoopt     Print strings on stdout, with opts
+
+The toplevelprog echoprog additional help topics are:
+   topic3      Help topic 3 short
+
+The toplevelprog echoprog flags are:
+ -extra=false
+   Print an extra arg
+================================================================================
+Toplevelprog Echoprog Echo
+
+Echo prints any strings passed in to stdout.
+
+Usage:
+   toplevelprog echoprog echo [strings]
+
+[strings] are arbitrary strings that will be echoed.
+================================================================================
+Toplevelprog Echoprog Echoopt
+
+Echoopt prints any args passed in to stdout.
+
+Usage:
+   toplevelprog echoprog echoopt [flags] [args]
+
+[args] are arbitrary strings that will be echoed.
+
+The toplevelprog echoprog echoopt flags are:
+ -n=false
+   Do not output trailing newline
+================================================================================
+Toplevelprog Echoprog Topic3 - help topic
+
+Help topic 3 long.
+================================================================================
+Toplevelprog Hello
+
+Hello prints any strings passed in to stdout preceded by "Hello".
+
+Usage:
+   toplevelprog hello [strings]
+
+[strings] are arbitrary strings that will be printed.
+================================================================================
+Toplevelprog Help
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+The output is formatted to a target width in runes.  The target width is
+determined by checking the environment variable CMDLINE_WIDTH, falling back on
+the terminal width from the OS, falling back on 80 chars.  By setting
+CMDLINE_WIDTH=x, if x > 0 the width is x, if x < 0 the width is unlimited, and
+if x == 0 or is unset one of the fallbacks is used.
+
+Usage:
+   toplevelprog help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The toplevelprog help flags are:
+ -style=text
+   The formatting style for help output, either "text" or "godoc".
+================================================================================
+Toplevelprog Topic1 - help topic
+
+Help topic 1 long.
+================================================================================
+Toplevelprog Topic2 - help topic
+
+Help topic 2 long.
+`,
+		},
+		{
+			Args: []string{"help", "echoprog"},
+			Stdout: `Echoprog has two variants of echo.
+
+Usage:
+   toplevelprog echoprog [flags] <command>
+
+The toplevelprog echoprog commands are:
+   echo        Print strings on stdout
+   echoopt     Print strings on stdout, with opts
+   help        Display help for commands or topics
+Run "toplevelprog echoprog help [command]" for command usage.
+
+The toplevelprog echoprog additional help topics are:
+   topic3      Help topic 3 short
+Run "toplevelprog echoprog help [topic]" for topic details.
+
+The toplevelprog echoprog flags are:
+ -extra=false
+   Print an extra arg
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args: []string{"help", "topic1"},
+			Stdout: `Help topic 1 long.
+`,
+		},
+		{
+			Args: []string{"help", "topic2"},
+			Stdout: `Help topic 2 long.
+`,
+		},
+		{
+			Args: []string{"echoprog", "help", "..."},
+			Stdout: `Echoprog has two variants of echo.
+
+Usage:
+   toplevelprog echoprog [flags] <command>
+
+The toplevelprog echoprog commands are:
+   echo        Print strings on stdout
+   echoopt     Print strings on stdout, with opts
+   help        Display help for commands or topics
+Run "toplevelprog echoprog help [command]" for command usage.
+
+The toplevelprog echoprog additional help topics are:
+   topic3      Help topic 3 short
+Run "toplevelprog echoprog help [topic]" for topic details.
+
+The toplevelprog echoprog flags are:
+ -extra=false
+   Print an extra arg
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+================================================================================
+Toplevelprog Echoprog Echo
+
+Echo prints any strings passed in to stdout.
+
+Usage:
+   toplevelprog echoprog echo [strings]
+
+[strings] are arbitrary strings that will be echoed.
+================================================================================
+Toplevelprog Echoprog Echoopt
+
+Echoopt prints any args passed in to stdout.
+
+Usage:
+   toplevelprog echoprog echoopt [flags] [args]
+
+[args] are arbitrary strings that will be echoed.
+
+The toplevelprog echoprog echoopt flags are:
+ -n=false
+   Do not output trailing newline
+================================================================================
+Toplevelprog Echoprog Help
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+The output is formatted to a target width in runes.  The target width is
+determined by checking the environment variable CMDLINE_WIDTH, falling back on
+the terminal width from the OS, falling back on 80 chars.  By setting
+CMDLINE_WIDTH=x, if x > 0 the width is x, if x < 0 the width is unlimited, and
+if x == 0 or is unset one of the fallbacks is used.
+
+Usage:
+   toplevelprog echoprog help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The toplevelprog echoprog help flags are:
+ -style=text
+   The formatting style for help output, either "text" or "godoc".
+================================================================================
+Toplevelprog Echoprog Topic3 - help topic
+
+Help topic 3 long.
+`,
+		},
+		{
+			Args: []string{"echoprog", "help", "echoopt"},
+			Stdout: `Echoopt prints any args passed in to stdout.
+
+Usage:
+   toplevelprog echoprog echoopt [flags] [args]
+
+[args] are arbitrary strings that will be echoed.
+
+The toplevelprog echoprog echoopt flags are:
+ -n=false
+   Do not output trailing newline
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args: []string{"help", "echoprog", "topic3"},
+			Stdout: `Help topic 3 long.
+`,
+		},
+		{
+			Args: []string{"echoprog", "help", "topic3"},
+			Stdout: `Help topic 3 long.
+`,
+		},
+		{
+			Args: []string{"help", "hello"},
+			Stdout: `Hello prints any strings passed in to stdout preceded by "Hello".
+
+Usage:
+   toplevelprog hello [strings]
+
+[strings] are arbitrary strings that will be printed.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args: []string{"help", "foo"},
+			Err:  ErrUsage,
+			Stderr: `ERROR: toplevelprog: unknown command or topic "foo"
+
+Toplevelprog has the echo subprogram and the hello command.
+
+Usage:
+   toplevelprog [flags] <command>
+
+The toplevelprog commands are:
+   echoprog    Set of echo commands
+   hello       Print strings on stdout preceded by "Hello"
+   help        Display help for commands or topics
+Run "toplevelprog help [command]" for command usage.
+
+The toplevelprog additional help topics are:
+   topic1      Help topic 1 short
+   topic2      Help topic 2 short
+Run "toplevelprog help [topic]" for topic details.
+
+The toplevelprog flags are:
+ -tlextra=false
+   Print an extra arg for all commands
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args:   []string{"echoprog", "echo", "foo", "bar"},
+			Stdout: "[foo bar]\n",
+		},
+		{
+			Args:   []string{"echoprog", "-extra", "echo", "foo", "bar"},
+			Stdout: "[foo bar extra]\n",
+		},
+		{
+			Args: []string{"echoprog", "echo", "error"},
+			Err:  errEcho,
+		},
+		{
+			Args:   []string{"echoprog", "echoopt", "foo", "bar"},
+			Stdout: "[foo bar]\n",
+		},
+		{
+			Args:   []string{"echoprog", "-extra", "echoopt", "foo", "bar"},
+			Stdout: "[foo bar extra]\n",
+		},
+		{
+			Args:   []string{"echoprog", "echoopt", "-n", "foo", "bar"},
+			Stdout: "[foo bar]",
+		},
+		{
+			Args:   []string{"echoprog", "-extra", "echoopt", "-n", "foo", "bar"},
+			Stdout: "[foo bar extra]",
+		},
+		{
+			Args: []string{"echoprog", "echoopt", "error"},
+			Err:  errEcho,
+		},
+		{
+			Args:   []string{"--tlextra", "echoprog", "-extra", "echoopt", "foo", "bar"},
+			Stdout: "[foo bar extra tlextra]\n",
+		},
+		{
+			Args:   []string{"hello", "foo", "bar"},
+			Stdout: "Hello foo bar\n",
+		},
+		{
+			Args:   []string{"--tlextra", "hello", "foo", "bar"},
+			Stdout: "Hello foo bar tlextra\n",
+		},
+		{
+			Args: []string{"hello", "--extra", "foo", "bar"},
+			Err:  ErrUsage,
+			Stderr: `ERROR: toplevelprog hello: flag provided but not defined: -extra
+
+Hello prints any strings passed in to stdout preceded by "Hello".
+
+Usage:
+   toplevelprog hello [strings]
+
+[strings] are arbitrary strings that will be printed.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args: []string{"-extra", "echoprog", "echoopt", "foo", "bar"},
+			Err:  ErrUsage,
+			Stderr: `ERROR: toplevelprog: flag provided but not defined: -extra
+
+Toplevelprog has the echo subprogram and the hello command.
+
+Usage:
+   toplevelprog [flags] <command>
+
+The toplevelprog commands are:
+   echoprog    Set of echo commands
+   hello       Print strings on stdout preceded by "Hello"
+   help        Display help for commands or topics
+Run "toplevelprog help [command]" for command usage.
+
+The toplevelprog additional help topics are:
+   topic1      Help topic 1 short
+   topic2      Help topic 2 short
+Run "toplevelprog help [topic]" for topic details.
+
+The toplevelprog flags are:
+ -tlextra=false
+   Print an extra arg for all commands
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+	}
+	runTestCases(t, prog, tests)
+}
+
+func TestMultiLevelCommandsOrdering(t *testing.T) {
+	cmdHello11 := &Command{
+		Name:  "hello11",
+		Short: "Print strings on stdout preceded by \"Hello\"",
+		Long: `
+Hello prints any strings passed in to stdout preceded by "Hello".
+`,
+		ArgsName: "[strings]",
+		ArgsLong: "[strings] are arbitrary strings that will be printed.",
+		Run:      runHello,
+	}
+	cmdHello12 := &Command{
+		Name:  "hello12",
+		Short: "Print strings on stdout preceded by \"Hello\"",
+		Long: `
+Hello prints any strings passed in to stdout preceded by "Hello".
+`,
+		ArgsName: "[strings]",
+		ArgsLong: "[strings] are arbitrary strings that will be printed.",
+		Run:      runHello,
+	}
+	cmdHello21 := &Command{
+		Name:  "hello21",
+		Short: "Print strings on stdout preceded by \"Hello\"",
+		Long: `
+Hello prints any strings passed in to stdout preceded by "Hello".
+`,
+		ArgsName: "[strings]",
+		ArgsLong: "[strings] are arbitrary strings that will be printed.",
+		Run:      runHello,
+	}
+	cmdHello22 := &Command{
+		Name:  "hello22",
+		Short: "Print strings on stdout preceded by \"Hello\"",
+		Long: `
+Hello prints any strings passed in to stdout preceded by "Hello".
+`,
+		ArgsName: "[strings]",
+		ArgsLong: "[strings] are arbitrary strings that will be printed.",
+		Run:      runHello,
+	}
+	cmdHello31 := &Command{
+		Name:  "hello31",
+		Short: "Print strings on stdout preceded by \"Hello\"",
+		Long: `
+Hello prints any strings passed in to stdout preceded by "Hello".
+`,
+		ArgsName: "[strings]",
+		ArgsLong: "[strings] are arbitrary strings that will be printed.",
+		Run:      runHello,
+	}
+	cmdHello32 := &Command{
+		Name:  "hello32",
+		Short: "Print strings on stdout preceded by \"Hello\"",
+		Long: `
+Hello prints any strings passed in to stdout preceded by "Hello".
+`,
+		ArgsName: "[strings]",
+		ArgsLong: "[strings] are arbitrary strings that will be printed.",
+		Run:      runHello,
+	}
+	progHello3 := &Command{
+		Name:     "prog3",
+		Short:    "Set of hello commands",
+		Long:     "Prog3 has two variants of hello.",
+		Children: []*Command{cmdHello31, cmdHello32},
+	}
+	progHello2 := &Command{
+		Name:     "prog2",
+		Short:    "Set of hello commands",
+		Long:     "Prog2 has two variants of hello and a subprogram prog3.",
+		Children: []*Command{cmdHello21, progHello3, cmdHello22},
+	}
+	progHello1 := &Command{
+		Name:     "prog1",
+		Short:    "Set of hello commands",
+		Long:     "Prog1 has two variants of hello and a subprogram prog2.",
+		Children: []*Command{cmdHello11, cmdHello12, progHello2},
+	}
+
+	var tests = []testCase{
+		{
+			Args: []string{},
+			Err:  ErrUsage,
+			Stderr: `ERROR: prog1: no command specified
+
+Prog1 has two variants of hello and a subprogram prog2.
+
+Usage:
+   prog1 <command>
+
+The prog1 commands are:
+   hello11     Print strings on stdout preceded by "Hello"
+   hello12     Print strings on stdout preceded by "Hello"
+   prog2       Set of hello commands
+   help        Display help for commands or topics
+Run "prog1 help [command]" for command usage.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args: []string{"help"},
+			Stdout: `Prog1 has two variants of hello and a subprogram prog2.
+
+Usage:
+   prog1 <command>
+
+The prog1 commands are:
+   hello11     Print strings on stdout preceded by "Hello"
+   hello12     Print strings on stdout preceded by "Hello"
+   prog2       Set of hello commands
+   help        Display help for commands or topics
+Run "prog1 help [command]" for command usage.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args: []string{"help", "..."},
+			Stdout: `Prog1 has two variants of hello and a subprogram prog2.
+
+Usage:
+   prog1 <command>
+
+The prog1 commands are:
+   hello11     Print strings on stdout preceded by "Hello"
+   hello12     Print strings on stdout preceded by "Hello"
+   prog2       Set of hello commands
+   help        Display help for commands or topics
+Run "prog1 help [command]" for command usage.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+================================================================================
+Prog1 Hello11
+
+Hello prints any strings passed in to stdout preceded by "Hello".
+
+Usage:
+   prog1 hello11 [strings]
+
+[strings] are arbitrary strings that will be printed.
+================================================================================
+Prog1 Hello12
+
+Hello prints any strings passed in to stdout preceded by "Hello".
+
+Usage:
+   prog1 hello12 [strings]
+
+[strings] are arbitrary strings that will be printed.
+================================================================================
+Prog1 Prog2
+
+Prog2 has two variants of hello and a subprogram prog3.
+
+Usage:
+   prog1 prog2 <command>
+
+The prog1 prog2 commands are:
+   hello21     Print strings on stdout preceded by "Hello"
+   prog3       Set of hello commands
+   hello22     Print strings on stdout preceded by "Hello"
+================================================================================
+Prog1 Prog2 Hello21
+
+Hello prints any strings passed in to stdout preceded by "Hello".
+
+Usage:
+   prog1 prog2 hello21 [strings]
+
+[strings] are arbitrary strings that will be printed.
+================================================================================
+Prog1 Prog2 Prog3
+
+Prog3 has two variants of hello.
+
+Usage:
+   prog1 prog2 prog3 <command>
+
+The prog1 prog2 prog3 commands are:
+   hello31     Print strings on stdout preceded by "Hello"
+   hello32     Print strings on stdout preceded by "Hello"
+================================================================================
+Prog1 Prog2 Prog3 Hello31
+
+Hello prints any strings passed in to stdout preceded by "Hello".
+
+Usage:
+   prog1 prog2 prog3 hello31 [strings]
+
+[strings] are arbitrary strings that will be printed.
+================================================================================
+Prog1 Prog2 Prog3 Hello32
+
+Hello prints any strings passed in to stdout preceded by "Hello".
+
+Usage:
+   prog1 prog2 prog3 hello32 [strings]
+
+[strings] are arbitrary strings that will be printed.
+================================================================================
+Prog1 Prog2 Hello22
+
+Hello prints any strings passed in to stdout preceded by "Hello".
+
+Usage:
+   prog1 prog2 hello22 [strings]
+
+[strings] are arbitrary strings that will be printed.
+================================================================================
+Prog1 Help
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+The output is formatted to a target width in runes.  The target width is
+determined by checking the environment variable CMDLINE_WIDTH, falling back on
+the terminal width from the OS, falling back on 80 chars.  By setting
+CMDLINE_WIDTH=x, if x > 0 the width is x, if x < 0 the width is unlimited, and
+if x == 0 or is unset one of the fallbacks is used.
+
+Usage:
+   prog1 help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The prog1 help flags are:
+ -style=text
+   The formatting style for help output, either "text" or "godoc".
+`,
+		},
+		{
+			Args: []string{"prog2", "help", "..."},
+			Stdout: `Prog2 has two variants of hello and a subprogram prog3.
+
+Usage:
+   prog1 prog2 <command>
+
+The prog1 prog2 commands are:
+   hello21     Print strings on stdout preceded by "Hello"
+   prog3       Set of hello commands
+   hello22     Print strings on stdout preceded by "Hello"
+   help        Display help for commands or topics
+Run "prog1 prog2 help [command]" for command usage.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+================================================================================
+Prog1 Prog2 Hello21
+
+Hello prints any strings passed in to stdout preceded by "Hello".
+
+Usage:
+   prog1 prog2 hello21 [strings]
+
+[strings] are arbitrary strings that will be printed.
+================================================================================
+Prog1 Prog2 Prog3
+
+Prog3 has two variants of hello.
+
+Usage:
+   prog1 prog2 prog3 <command>
+
+The prog1 prog2 prog3 commands are:
+   hello31     Print strings on stdout preceded by "Hello"
+   hello32     Print strings on stdout preceded by "Hello"
+================================================================================
+Prog1 Prog2 Prog3 Hello31
+
+Hello prints any strings passed in to stdout preceded by "Hello".
+
+Usage:
+   prog1 prog2 prog3 hello31 [strings]
+
+[strings] are arbitrary strings that will be printed.
+================================================================================
+Prog1 Prog2 Prog3 Hello32
+
+Hello prints any strings passed in to stdout preceded by "Hello".
+
+Usage:
+   prog1 prog2 prog3 hello32 [strings]
+
+[strings] are arbitrary strings that will be printed.
+================================================================================
+Prog1 Prog2 Hello22
+
+Hello prints any strings passed in to stdout preceded by "Hello".
+
+Usage:
+   prog1 prog2 hello22 [strings]
+
+[strings] are arbitrary strings that will be printed.
+================================================================================
+Prog1 Prog2 Help
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+The output is formatted to a target width in runes.  The target width is
+determined by checking the environment variable CMDLINE_WIDTH, falling back on
+the terminal width from the OS, falling back on 80 chars.  By setting
+CMDLINE_WIDTH=x, if x > 0 the width is x, if x < 0 the width is unlimited, and
+if x == 0 or is unset one of the fallbacks is used.
+
+Usage:
+   prog1 prog2 help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The prog1 prog2 help flags are:
+ -style=text
+   The formatting style for help output, either "text" or "godoc".
+`,
+		},
+		{
+			Args: []string{"prog2", "prog3", "help", "..."},
+			Stdout: `Prog3 has two variants of hello.
+
+Usage:
+   prog1 prog2 prog3 <command>
+
+The prog1 prog2 prog3 commands are:
+   hello31     Print strings on stdout preceded by "Hello"
+   hello32     Print strings on stdout preceded by "Hello"
+   help        Display help for commands or topics
+Run "prog1 prog2 prog3 help [command]" for command usage.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+================================================================================
+Prog1 Prog2 Prog3 Hello31
+
+Hello prints any strings passed in to stdout preceded by "Hello".
+
+Usage:
+   prog1 prog2 prog3 hello31 [strings]
+
+[strings] are arbitrary strings that will be printed.
+================================================================================
+Prog1 Prog2 Prog3 Hello32
+
+Hello prints any strings passed in to stdout preceded by "Hello".
+
+Usage:
+   prog1 prog2 prog3 hello32 [strings]
+
+[strings] are arbitrary strings that will be printed.
+================================================================================
+Prog1 Prog2 Prog3 Help
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+The output is formatted to a target width in runes.  The target width is
+determined by checking the environment variable CMDLINE_WIDTH, falling back on
+the terminal width from the OS, falling back on 80 chars.  By setting
+CMDLINE_WIDTH=x, if x > 0 the width is x, if x < 0 the width is unlimited, and
+if x == 0 or is unset one of the fallbacks is used.
+
+Usage:
+   prog1 prog2 prog3 help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The prog1 prog2 prog3 help flags are:
+ -style=text
+   The formatting style for help output, either "text" or "godoc".
+`,
+		},
+		{
+			Args: []string{"help", "prog2", "prog3", "..."},
+			Stdout: `Prog3 has two variants of hello.
+
+Usage:
+   prog1 prog2 prog3 <command>
+
+The prog1 prog2 prog3 commands are:
+   hello31     Print strings on stdout preceded by "Hello"
+   hello32     Print strings on stdout preceded by "Hello"
+   help        Display help for commands or topics
+Run "prog1 prog2 prog3 help [command]" for command usage.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+================================================================================
+Prog1 Prog2 Prog3 Hello31
+
+Hello prints any strings passed in to stdout preceded by "Hello".
+
+Usage:
+   prog1 prog2 prog3 hello31 [strings]
+
+[strings] are arbitrary strings that will be printed.
+================================================================================
+Prog1 Prog2 Prog3 Hello32
+
+Hello prints any strings passed in to stdout preceded by "Hello".
+
+Usage:
+   prog1 prog2 prog3 hello32 [strings]
+
+[strings] are arbitrary strings that will be printed.
+================================================================================
+Prog1 Prog2 Prog3 Help
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+The output is formatted to a target width in runes.  The target width is
+determined by checking the environment variable CMDLINE_WIDTH, falling back on
+the terminal width from the OS, falling back on 80 chars.  By setting
+CMDLINE_WIDTH=x, if x > 0 the width is x, if x < 0 the width is unlimited, and
+if x == 0 or is unset one of the fallbacks is used.
+
+Usage:
+   prog1 prog2 prog3 help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The prog1 prog2 prog3 help flags are:
+ -style=text
+   The formatting style for help output, either "text" or "godoc".
+`,
+		},
+		{
+			Args: []string{"help", "-style=godoc", "..."},
+			Stdout: `Prog1 has two variants of hello and a subprogram prog2.
+
+Usage:
+   prog1 <command>
+
+The prog1 commands are:
+   hello11     Print strings on stdout preceded by "Hello"
+   hello12     Print strings on stdout preceded by "Hello"
+   prog2       Set of hello commands
+   help        Display help for commands or topics
+Run "prog1 help [command]" for command usage.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+
+Prog1 Hello11
+
+Hello prints any strings passed in to stdout preceded by "Hello".
+
+Usage:
+   prog1 hello11 [strings]
+
+[strings] are arbitrary strings that will be printed.
+
+Prog1 Hello12
+
+Hello prints any strings passed in to stdout preceded by "Hello".
+
+Usage:
+   prog1 hello12 [strings]
+
+[strings] are arbitrary strings that will be printed.
+
+Prog1 Prog2
+
+Prog2 has two variants of hello and a subprogram prog3.
+
+Usage:
+   prog1 prog2 <command>
+
+The prog1 prog2 commands are:
+   hello21     Print strings on stdout preceded by "Hello"
+   prog3       Set of hello commands
+   hello22     Print strings on stdout preceded by "Hello"
+
+Prog1 Prog2 Hello21
+
+Hello prints any strings passed in to stdout preceded by "Hello".
+
+Usage:
+   prog1 prog2 hello21 [strings]
+
+[strings] are arbitrary strings that will be printed.
+
+Prog1 Prog2 Prog3
+
+Prog3 has two variants of hello.
+
+Usage:
+   prog1 prog2 prog3 <command>
+
+The prog1 prog2 prog3 commands are:
+   hello31     Print strings on stdout preceded by "Hello"
+   hello32     Print strings on stdout preceded by "Hello"
+
+Prog1 Prog2 Prog3 Hello31
+
+Hello prints any strings passed in to stdout preceded by "Hello".
+
+Usage:
+   prog1 prog2 prog3 hello31 [strings]
+
+[strings] are arbitrary strings that will be printed.
+
+Prog1 Prog2 Prog3 Hello32
+
+Hello prints any strings passed in to stdout preceded by "Hello".
+
+Usage:
+   prog1 prog2 prog3 hello32 [strings]
+
+[strings] are arbitrary strings that will be printed.
+
+Prog1 Prog2 Hello22
+
+Hello prints any strings passed in to stdout preceded by "Hello".
+
+Usage:
+   prog1 prog2 hello22 [strings]
+
+[strings] are arbitrary strings that will be printed.
+
+Prog1 Help
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+The output is formatted to a target width in runes.  The target width is
+determined by checking the environment variable CMDLINE_WIDTH, falling back on
+the terminal width from the OS, falling back on 80 chars.  By setting
+CMDLINE_WIDTH=x, if x > 0 the width is x, if x < 0 the width is unlimited, and
+if x == 0 or is unset one of the fallbacks is used.
+
+Usage:
+   prog1 help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The prog1 help flags are:
+ -style=text
+   The formatting style for help output, either "text" or "godoc".
+`,
+		},
+	}
+
+	runTestCases(t, progHello1, tests)
+}
+
+func TestCommandAndArgs(t *testing.T) {
+	cmdEcho := &Command{
+		Name:  "echo",
+		Short: "Print strings on stdout",
+		Long: `
+Echo prints any strings passed in to stdout.
+`,
+		Run:      runEcho,
+		ArgsName: "[strings]",
+		ArgsLong: "[strings] are arbitrary strings that will be echoed.",
+	}
+
+	prog := &Command{
+		Name:     "cmdargs",
+		Short:    "Cmdargs program.",
+		Long:     "Cmdargs has the echo command and a Run function with args.",
+		Children: []*Command{cmdEcho},
+		Run:      runHello,
+		ArgsName: "[strings]",
+		ArgsLong: "[strings] are arbitrary strings that will be printed.",
+	}
+
+	var tests = []testCase{
+		{
+			Args:   []string{},
+			Stdout: "Hello\n",
+		},
+		{
+			Args:   []string{"foo"},
+			Stdout: "Hello foo\n",
+		},
+		{
+			Args: []string{"help"},
+			Stdout: `Cmdargs has the echo command and a Run function with args.
+
+Usage:
+   cmdargs <command>
+   cmdargs [strings]
+
+The cmdargs commands are:
+   echo        Print strings on stdout
+   help        Display help for commands or topics
+Run "cmdargs help [command]" for command usage.
+
+[strings] are arbitrary strings that will be printed.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args: []string{"help", "echo"},
+			Stdout: `Echo prints any strings passed in to stdout.
+
+Usage:
+   cmdargs echo [strings]
+
+[strings] are arbitrary strings that will be echoed.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args: []string{"help", "..."},
+			Stdout: `Cmdargs has the echo command and a Run function with args.
+
+Usage:
+   cmdargs <command>
+   cmdargs [strings]
+
+The cmdargs commands are:
+   echo        Print strings on stdout
+   help        Display help for commands or topics
+Run "cmdargs help [command]" for command usage.
+
+[strings] are arbitrary strings that will be printed.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+================================================================================
+Cmdargs Echo
+
+Echo prints any strings passed in to stdout.
+
+Usage:
+   cmdargs echo [strings]
+
+[strings] are arbitrary strings that will be echoed.
+================================================================================
+Cmdargs Help
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+The output is formatted to a target width in runes.  The target width is
+determined by checking the environment variable CMDLINE_WIDTH, falling back on
+the terminal width from the OS, falling back on 80 chars.  By setting
+CMDLINE_WIDTH=x, if x > 0 the width is x, if x < 0 the width is unlimited, and
+if x == 0 or is unset one of the fallbacks is used.
+
+Usage:
+   cmdargs help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The cmdargs help flags are:
+ -style=text
+   The formatting style for help output, either "text" or "godoc".
+`,
+		},
+		{
+			Args: []string{"help", "foo"},
+			Err:  ErrUsage,
+			Stderr: `ERROR: cmdargs: unknown command or topic "foo"
+
+Cmdargs has the echo command and a Run function with args.
+
+Usage:
+   cmdargs <command>
+   cmdargs [strings]
+
+The cmdargs commands are:
+   echo        Print strings on stdout
+   help        Display help for commands or topics
+Run "cmdargs help [command]" for command usage.
+
+[strings] are arbitrary strings that will be printed.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args:   []string{"echo", "foo", "bar"},
+			Stdout: "[foo bar]\n",
+		},
+		{
+			Args: []string{"echo", "error"},
+			Err:  errEcho,
+		},
+		{
+			Args: []string{"echo", "bad_arg"},
+			Err:  ErrUsage,
+			Stderr: `ERROR: Invalid argument bad_arg
+
+Echo prints any strings passed in to stdout.
+
+Usage:
+   cmdargs echo [strings]
+
+[strings] are arbitrary strings that will be echoed.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+	}
+	runTestCases(t, prog, tests)
+}
+
+func TestCommandAndRunNoArgs(t *testing.T) {
+	cmdEcho := &Command{
+		Name:  "echo",
+		Short: "Print strings on stdout",
+		Long: `
+Echo prints any strings passed in to stdout.
+`,
+		Run:      runEcho,
+		ArgsName: "[strings]",
+		ArgsLong: "[strings] are arbitrary strings that will be echoed.",
+	}
+
+	prog := &Command{
+		Name:     "cmdrun",
+		Short:    "Cmdrun program.",
+		Long:     "Cmdrun has the echo command and a Run function with no args.",
+		Children: []*Command{cmdEcho},
+		Run:      runHello,
+	}
+
+	var tests = []testCase{
+		{
+			Args:   []string{},
+			Stdout: "Hello\n",
+		},
+		{
+			Args: []string{"foo"},
+			Err:  ErrUsage,
+			Stderr: `ERROR: cmdrun: unknown command "foo"
+
+Cmdrun has the echo command and a Run function with no args.
+
+Usage:
+   cmdrun <command>
+   cmdrun
+
+The cmdrun commands are:
+   echo        Print strings on stdout
+   help        Display help for commands or topics
+Run "cmdrun help [command]" for command usage.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args: []string{"help"},
+			Stdout: `Cmdrun has the echo command and a Run function with no args.
+
+Usage:
+   cmdrun <command>
+   cmdrun
+
+The cmdrun commands are:
+   echo        Print strings on stdout
+   help        Display help for commands or topics
+Run "cmdrun help [command]" for command usage.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args: []string{"help", "echo"},
+			Stdout: `Echo prints any strings passed in to stdout.
+
+Usage:
+   cmdrun echo [strings]
+
+[strings] are arbitrary strings that will be echoed.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args: []string{"help", "..."},
+			Stdout: `Cmdrun has the echo command and a Run function with no args.
+
+Usage:
+   cmdrun <command>
+   cmdrun
+
+The cmdrun commands are:
+   echo        Print strings on stdout
+   help        Display help for commands or topics
+Run "cmdrun help [command]" for command usage.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+================================================================================
+Cmdrun Echo
+
+Echo prints any strings passed in to stdout.
+
+Usage:
+   cmdrun echo [strings]
+
+[strings] are arbitrary strings that will be echoed.
+================================================================================
+Cmdrun Help
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+The output is formatted to a target width in runes.  The target width is
+determined by checking the environment variable CMDLINE_WIDTH, falling back on
+the terminal width from the OS, falling back on 80 chars.  By setting
+CMDLINE_WIDTH=x, if x > 0 the width is x, if x < 0 the width is unlimited, and
+if x == 0 or is unset one of the fallbacks is used.
+
+Usage:
+   cmdrun help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The cmdrun help flags are:
+ -style=text
+   The formatting style for help output, either "text" or "godoc".
+`,
+		},
+		{
+			Args: []string{"help", "foo"},
+			Err:  ErrUsage,
+			Stderr: `ERROR: cmdrun: unknown command or topic "foo"
+
+Cmdrun has the echo command and a Run function with no args.
+
+Usage:
+   cmdrun <command>
+   cmdrun
+
+The cmdrun commands are:
+   echo        Print strings on stdout
+   help        Display help for commands or topics
+Run "cmdrun help [command]" for command usage.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args:   []string{"echo", "foo", "bar"},
+			Stdout: "[foo bar]\n",
+		},
+		{
+			Args: []string{"echo", "error"},
+			Err:  errEcho,
+		},
+		{
+			Args: []string{"echo", "bad_arg"},
+			Err:  ErrUsage,
+			Stderr: `ERROR: Invalid argument bad_arg
+
+Echo prints any strings passed in to stdout.
+
+Usage:
+   cmdrun echo [strings]
+
+[strings] are arbitrary strings that will be echoed.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+	}
+	runTestCases(t, prog, tests)
+}
+
+func TestLongCommandsHelp(t *testing.T) {
+	cmdLong := &Command{
+		Name:  "thisisaverylongcommand",
+		Short: "the short description of the very long command is very long, and will have to be wrapped",
+		Long:  "The long description of the very long command is also very long, and will similarly have to be wrapped",
+		Run:   runEcho,
+	}
+	cmdShort := &Command{
+		Name:  "x",
+		Short: "description of short command.",
+		Long:  "blah blah blah",
+		Run:   runEcho,
+	}
+	prog := &Command{
+		Name:     "program",
+		Short:    "Test help strings when there are long commands.",
+		Long:     "Test help strings when there are long commands.",
+		Children: []*Command{cmdShort, cmdLong},
+	}
+	var tests = []testCase{
+		{
+			Args: []string{"help"},
+			Stdout: `Test help strings when there are long commands.
+
+Usage:
+   program <command>
+
+The program commands are:
+   x                      description of short command.
+   thisisaverylongcommand the short description of the very long command is very
+                          long, and will have to be wrapped
+   help                   Display help for commands or topics
+Run "program help [command]" for command usage.
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+		{
+			Args: []string{"help", "thisisaverylongcommand"},
+			Stdout: `The long description of the very long command is also very long, and will
+similarly have to be wrapped
+
+Usage:
+   program thisisaverylongcommand
+
+The global flags are:
+ -global1=
+   global test flag 1
+ -global2=0
+   global test flag 2
+`,
+		},
+	}
+	runTestCases(t, prog, tests)
+}
diff --git a/cmdline/testdata/gendoc.go b/cmdline/testdata/gendoc.go
new file mode 100644
index 0000000..02ecc5c
--- /dev/null
+++ b/cmdline/testdata/gendoc.go
@@ -0,0 +1,83 @@
+// Command gendoc can be used for generating detailed godoc comments
+// for cmdline-based tools. The user specifies the cmdline-based tool
+// source file directory <dir> using the first command-line argument
+// and gendoc executes the tool with flags that generate detailed
+// godoc comment and output it to <dir>/doc.go. If more than one
+// command-line argument is provided, they are passed through to the
+// tool the gendoc executes.
+//
+// NOTE: The reason this command is located in under a testdata
+// directory is to enforce its idiomatic use through "go run
+// <path>/testdata/gendoc.go <dir> [args]".
+//
+// NOTE: The gendoc command itself is not based on the cmdline library
+// to avoid non-trivial bootstrapping. In particular, if the
+// compilation of gendoc requires GOPATH to contain the vanadium Go
+// workspaces, then running the gendoc command requires the v23 tool,
+// which in turn my depend on the gendoc command.
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"strings"
+)
+
+func main() {
+	if err := generate(); err != nil {
+		fmt.Fprintln(os.Stderr, err)
+		os.Exit(1)
+	}
+}
+
+func generate() error {
+	if got, want := len(os.Args[1:]), 1; got < want {
+		return fmt.Errorf("gendoc requires at least one argument\nusage: gendoc <dir> [args]")
+	}
+	pkg := os.Args[1]
+
+	// Build the gendoc binary in a temporary folder.
+	tmpDir, err := ioutil.TempDir("", "")
+	if err != nil {
+		return fmt.Errorf("TempDir() failed: %v", err)
+	}
+	defer os.RemoveAll(tmpDir)
+	gendocBin := filepath.Join(tmpDir, "gendoc")
+	args := []string{"go", "build", "-o", gendocBin}
+	args = append(args, pkg)
+	buildCmd := exec.Command("v23", args...)
+	if err := buildCmd.Run(); err != nil {
+		return fmt.Errorf("%q failed: %v\n", strings.Join(buildCmd.Args, " "), err)
+	}
+
+	// Use it to generate the documentation.
+	var out bytes.Buffer
+	if len(os.Args) == 2 {
+		args = []string{"help", "-style=godoc", "..."}
+	} else {
+		args = os.Args[2:]
+	}
+	runCmd := exec.Command(gendocBin, args...)
+	runCmd.Stdout = &out
+	if err := runCmd.Run(); err != nil {
+		return fmt.Errorf("%q failed: %v\n%v\n", strings.Join(runCmd.Args, " "), err)
+	}
+	doc := fmt.Sprintf(`// This file was auto-generated via go generate.
+// DO NOT UPDATE MANUALLY
+
+/*
+%s*/
+package main
+`, out.String())
+
+	// Write the result to doc.go.
+	path, perm := filepath.Join(pkg, "doc.go"), os.FileMode(0644)
+	if err := ioutil.WriteFile(path, []byte(doc), perm); err != nil {
+		return fmt.Errorf("WriteFile(%v, %v) failed: %v\n", path, perm, err)
+	}
+	return nil
+}
diff --git a/dbutil/mysql.go b/dbutil/mysql.go
new file mode 100644
index 0000000..a1e4eee
--- /dev/null
+++ b/dbutil/mysql.go
@@ -0,0 +1,246 @@
+// Utility functions for opening and configuring a connection to a MySQL-like
+// database, with optional TLS support.
+//
+// Functions in this file are not thread-safe. However, the returned *sql.DB is.
+// Sane defaults are assumed: utf8mb4 encoding, UTC timezone, parsing date/time
+// into time.Time.
+
+package dbutil
+
+import (
+	"crypto/sha256"
+	"crypto/tls"
+	"crypto/x509"
+	"database/sql"
+	"encoding/hex"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/url"
+	"path/filepath"
+	"strings"
+
+	"github.com/go-sql-driver/mysql"
+)
+
+// SQL statement suffix to be appended when creating tables.
+const SqlCreateTableSuffix = "CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci"
+
+// Description of the SQL configuration file format.
+const SqlConfigFileDescription = `File must contain a JSON object of the following form:
+   {
+    "dataSourceName": "[username[:password]@][protocol[(address)]]/dbname", (the connection string required by go-sql-driver; database name must be specified, query parameters are not supported)
+    "tlsDisable": "false|true", (defaults to false; if set to true, uses an unencrypted connection; otherwise, the following fields are mandatory)
+    "tlsServerName": "serverName", (the domain name of the SQL server for TLS)
+    "rootCertPath": "[/]path/server-ca.pem", (the root certificate of the SQL server for TLS)
+    "clientCertPath": "[/]path/client-cert.pem", (the client certificate for TLS)
+    "clientKeyPath": "[/]path/client-key.pem" (the client private key for TLS)
+   }
+Paths must be either absolute or relative to the configuration file directory.`
+
+// SqlConfig holds the fields needed to connect to a SQL instance and to
+// configure TLS encryption of the information sent over the wire. It must be
+// activated via Activate() before use.
+type SqlConfig struct {
+	// DataSourceName is the connection string as required by go-sql-driver:
+	// "[username[:password]@][protocol[(address)]]/dbname";
+	// database name must be specified, query parameters are not supported.
+	DataSourceName string `json:"dataSourceName"`
+	// TLSDisable, if set to true, uses an unencrypted connection;
+	// otherwise, the following fields are mandatory.
+	TLSDisable bool `json:"tlsDisable"`
+	// TLSServerName is the domain name of the SQL server for TLS.
+	TLSServerName string `json:"tlsServerName"`
+	// RootCertPath is the root certificate of the SQL server for TLS.
+	RootCertPath string `json:"rootCertPath"`
+	// ClientCertPath is the client certificate for TLS.
+	ClientCertPath string `json:"clientCertPath"`
+	// ClientKeyPath is the client private key for TLS.
+	ClientKeyPath string `json:"clientKeyPath"`
+}
+
+// ActiveSqlConfig represents a SQL configuration that has been activated
+// by registering the TLS configuration (if applicable). It can be used for
+// opening SQL database connections.
+type ActiveSqlConfig struct {
+	// cfg is a copy of the SqlConfig that was activated.
+	cfg *SqlConfig
+	// tlsConfigIdentifier is the identifier under which the TLS configuration
+	// is registered with go-sql-driver. It is computed as a secure hash of the
+	// SqlConfig after resolving any relative paths.
+	tlsConfigIdentifier string `json:"-"`
+}
+
+// Parses the SQL configuration file pointed to by sqlConfigFile (format
+// described in SqlConfigFileDescription; also see links below).
+// https://github.com/go-sql-driver/mysql/#dsn-data-source-name
+// https://github.com/go-sql-driver/mysql/#tls
+func ParseSqlConfigFromFile(sqlConfigFile string) (*SqlConfig, error) {
+	configJSON, err := ioutil.ReadFile(sqlConfigFile)
+	if err != nil {
+		return nil, fmt.Errorf("failed reading SQL config file %q: %v", sqlConfigFile, err)
+	}
+	var config SqlConfig
+	if err = json.Unmarshal(configJSON, &config); err != nil {
+		// TODO(ivanpi): Parsing errors might leak the SQL password into error
+		// logs, depending on standard library implementation.
+		return nil, fmt.Errorf("failed parsing SQL config file %q: %v", sqlConfigFile, err)
+	}
+	return &config, nil
+}
+
+// Activates the SQL configuration by registering the TLS configuration with
+// go-mysql-driver (if TLSDisable is not set).
+// Certificate paths from SqlConfig that aren't absolute are interpreted relative
+// to certBaseDir.
+// For more information see https://github.com/go-sql-driver/mysql/#tls
+func (sc *SqlConfig) Activate(certBaseDir string) (*ActiveSqlConfig, error) {
+	if sc.TLSDisable {
+		return &ActiveSqlConfig{
+			cfg: sc.normalizePaths(""),
+		}, nil
+	}
+	cbdAbs, err := filepath.Abs(certBaseDir)
+	if err != nil {
+		return nil, fmt.Errorf("failed resolving certificate base directory %q: %v", certBaseDir, err)
+	}
+	scn := sc.normalizePaths(cbdAbs)
+	configId := scn.hash()
+	if err = registerSqlTLSConfig(scn, configId); err != nil {
+		return nil, fmt.Errorf("failed registering TLS config: %v", err)
+	}
+	return &ActiveSqlConfig{
+		cfg:                 scn,
+		tlsConfigIdentifier: configId,
+	}, nil
+}
+
+// Convenience function to parse and activate the SQL configuration file.
+// Certificate paths that aren't absolute are interpreted relative to the
+// directory containing sqlConfigFile.
+func ActivateSqlConfigFromFile(sqlConfigFile string) (*ActiveSqlConfig, error) {
+	cfg, err := ParseSqlConfigFromFile(sqlConfigFile)
+	if err != nil {
+		return nil, err
+	}
+	activeCfg, err := cfg.Activate(filepath.Dir(sqlConfigFile))
+	if err != nil {
+		return nil, fmt.Errorf("failed activating SQL config from file %q: %v", sqlConfigFile, err)
+	}
+	return activeCfg, nil
+}
+
+// Opens a connection to the SQL database using the provided configuration.
+// Sets the specified transaction isolation (see link below).
+// https://dev.mysql.com/doc/refman/5.5/en/server-system-variables.html#sysvar_tx_isolation
+func (sqlConfig *ActiveSqlConfig) NewSqlDBConn(txIsolation string) (*sql.DB, error) {
+	return openSqlDBConn(configureSqlDBConn(sqlConfig, txIsolation))
+}
+
+// Convenience function to parse and activate the configuration file and open
+// a connection to the SQL database. If multiple connections with the same
+// configuration are needed, a single ActivateSqlConfigFromFile() and multiple
+// NewSqlDbConn() calls are recommended instead.
+func NewSqlDBConnFromFile(sqlConfigFile, txIsolation string) (*sql.DB, error) {
+	config, err := ActivateSqlConfigFromFile(sqlConfigFile)
+	if err != nil {
+		return nil, err
+	}
+	return config.NewSqlDBConn(txIsolation)
+}
+
+func configureSqlDBConn(sqlConfig *ActiveSqlConfig, txIsolation string) string {
+	params := url.Values{}
+	// Setting charset is unneccessary when collation is set, according to
+	// https://github.com/go-sql-driver/mysql/#charset
+	params.Set("collation", "utf8mb4_general_ci")
+	// Maps SQL date/time values into time.Time instead of strings.
+	params.Set("parseTime", "true")
+	params.Set("loc", "UTC")
+	params.Set("time_zone", "'+00:00'")
+	if !sqlConfig.cfg.TLSDisable {
+		params.Set("tls", sqlConfig.tlsConfigIdentifier)
+	}
+	params.Set("tx_isolation", "'"+txIsolation+"'")
+	return sqlConfig.cfg.DataSourceName + "?" + params.Encode()
+}
+
+func openSqlDBConn(dataSrcName string) (*sql.DB, error) {
+	// Prevent leaking the SQL password into error logs.
+	sanitizedDSN := dataSrcName[strings.LastIndex(dataSrcName, "@")+1:]
+	db, err := sql.Open("mysql", dataSrcName)
+	if err != nil {
+		return nil, fmt.Errorf("failed opening database connection at %q: %v", sanitizedDSN, err)
+	}
+	if err := db.Ping(); err != nil {
+		return nil, fmt.Errorf("failed connecting to database at %q: %v", sanitizedDSN, err)
+	}
+	return db, nil
+}
+
+// registerSqlTLSConfig sets up the SQL connection to use TLS encryption
+// and registers the configuration under configId.
+// For more information see https://github.com/go-sql-driver/mysql/#tls
+func registerSqlTLSConfig(cfg *SqlConfig, configId string) error {
+	rootCertPool := x509.NewCertPool()
+	pem, err := ioutil.ReadFile(cfg.RootCertPath)
+	if err != nil {
+		return fmt.Errorf("failed reading root certificate: %v", err)
+	}
+	if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
+		return fmt.Errorf("failed to append PEM to cert pool")
+	}
+	ckpair, err := tls.LoadX509KeyPair(cfg.ClientCertPath, cfg.ClientKeyPath)
+	if err != nil {
+		return fmt.Errorf("failed loading client key pair: %v", err)
+	}
+	clientCert := []tls.Certificate{ckpair}
+	return mysql.RegisterTLSConfig(configId, &tls.Config{
+		RootCAs:      rootCertPool,
+		Certificates: clientCert,
+		ServerName:   cfg.TLSServerName,
+		// SSLv3 is more vulnerable than TLSv1.0, see https://en.wikipedia.org/wiki/POODLE
+		// TODO(ivanpi): Increase when Cloud SQL starts supporting higher TLS versions.
+		MinVersion: tls.VersionTLS10,
+		ClientAuth: tls.RequireAndVerifyClientCert,
+	})
+}
+
+// Computes a secure hash of the SqlConfig. Paths are canonicalized before
+// hashing.
+func (sc *SqlConfig) hash() string {
+	scn := sc.normalizePaths("")
+	fieldsToHash := []interface{}{
+		scn.DataSourceName, scn.TLSDisable, scn.TLSServerName,
+		scn.RootCertPath, scn.ClientCertPath, scn.ClientKeyPath,
+	}
+	hashAcc := make([]byte, 0, len(fieldsToHash)*sha256.Size)
+	for _, field := range fieldsToHash {
+		fieldHash := sha256.Sum256([]byte(fmt.Sprintf("%v", field)))
+		hashAcc = append(hashAcc, fieldHash[:]...)
+	}
+	structHash := sha256.Sum256(hashAcc)
+	return hex.EncodeToString(structHash[:])
+}
+
+// Returns a copy of the SqlConfig with certificate paths canonicalized and
+// resolved relative to certBaseDir. Blank paths remain blank.
+func (sc *SqlConfig) normalizePaths(certBaseDir string) *SqlConfig {
+	scn := *sc
+	for _, path := range []*string{&scn.RootCertPath, &scn.ClientCertPath, &scn.ClientKeyPath} {
+		*path = normalizePath(*path, certBaseDir)
+	}
+	return &scn
+}
+
+// If path is not absolute, resolves path relative to baseDir. Otherwise,
+// canonicalizes path. Blank paths are not resolved or canonicalized.
+func normalizePath(path, baseDir string) string {
+	if strings.TrimSpace(path) == "" {
+		return ""
+	}
+	if filepath.IsAbs(path) {
+		return filepath.Clean(path)
+	}
+	return filepath.Join(baseDir, path)
+}
diff --git a/netstate/isgloballyroutable.go b/netstate/isgloballyroutable.go
new file mode 100644
index 0000000..ee6bae5
--- /dev/null
+++ b/netstate/isgloballyroutable.go
@@ -0,0 +1,29 @@
+package netstate
+
+import (
+	"net"
+)
+
+var privateCIDRs = []net.IPNet{
+	net.IPNet{IP: net.IPv4(10, 0, 0, 0), Mask: net.IPv4Mask(0xff, 0, 0, 0)},
+	net.IPNet{IP: net.IPv4(172, 16, 0, 0), Mask: net.IPv4Mask(0xff, 0xf0, 0, 0)},
+	net.IPNet{IP: net.IPv4(192, 168, 0, 0), Mask: net.IPv4Mask(0xff, 0xff, 0, 0)},
+}
+
+// IsGloballyRoutable returns true if the argument is a globally routable IP address.
+func IsGloballyRoutableIP(ip net.IP) bool {
+	if !ip.IsGlobalUnicast() {
+		return false
+	}
+	if ip4 := ip.To4(); ip4 != nil {
+		for _, cidr := range privateCIDRs {
+			if cidr.Contains(ip4) {
+				return false
+			}
+		}
+		if ip4.Equal(net.IPv4bcast) {
+			return false
+		}
+	}
+	return true
+}
diff --git a/netstate/isgloballyroutable_test.go b/netstate/isgloballyroutable_test.go
new file mode 100644
index 0000000..dd0885a
--- /dev/null
+++ b/netstate/isgloballyroutable_test.go
@@ -0,0 +1,31 @@
+package netstate
+
+import (
+	"net"
+	"testing"
+)
+
+func TestIsGloballyRoutable(t *testing.T) {
+	tests := []struct {
+		ip   string
+		want bool
+	}{
+		{"192.168.1.1", false},
+		{"192.169.0.3", true},
+		{"10.1.1.1", false},
+		{"172.17.100.255", false},
+		{"172.32.0.1", true},
+		{"255.255.255.255", false},
+		{"127.0.0.1", false},
+		{"224.0.0.1", false},
+		{"FF02::FB", false},
+		{"fe80::be30:5bff:fed3:843f", false},
+		{"2620:0:1000:8400:be30:5bff:fed3:843f", true},
+	}
+	for _, test := range tests {
+		ip := net.ParseIP(test.ip)
+		if got := IsGloballyRoutableIP(ip); got != test.want {
+			t.Fatalf("%s: want %v got %v", test.ip, test.want, got)
+		}
+	}
+}
diff --git a/netstate/netstate.go b/netstate/netstate.go
new file mode 100644
index 0000000..623e7b2
--- /dev/null
+++ b/netstate/netstate.go
@@ -0,0 +1,398 @@
+// Package netstate provides routines to obtain the available set of
+// of network addresess, for determining changes to those addresses and for
+// selecting from amongst them according to some set of policies that are
+// implemented by applying simple predicates (functions with names of the form
+// Is<condition>) to filter or find the first matching address from a list
+// of addresses. The intent is to make it easy to create policies that do
+// things like 'find the first IPv4 unicast address that is globally routable,
+// failing that use a private IPv4 address, and failing that, an IPv6 address'.
+//
+// A simple usage would be:
+//
+//   state, _ := netstate.GetAccessibleIPs()
+//   ipv4 := state.Filter(netstate.IsPublicUnicastIPv4)
+//   // ipv4 will contain all of the public IPv4 addresses, if any.
+//
+// The example policy described above would be implemented using a
+// series of calls to Filter with appropriate predicates.
+//
+// In some cases, it may be necessary to take IP routing information
+// into account and hence interface hosting the address. The interface
+// hosting each address is provided in the AddrIfc structure used to represent
+// addresses and the IP routing information is provided by the GetAccessibleIPs
+// function which will typically be used to obtain the available IP addresses.
+//
+// Although most commercial networking hardware supports IPv6, some consumer
+// devices and more importantly many ISPs do not, so routines are provided
+// to allow developers to easily distinguish between the two and to use
+// whichever is appropriate for their product/situation.
+//
+// The term 'accessible' is used to refer to any non-loopback IP address.
+// The term 'public' is used to refer to any globally routable IP address.
+//
+// All IPv6 addresses are intended to be 'public', but any starting with
+// fc00::/7 (RFC4193) are reserved for private use, but the go
+// net libraries do not appear to recognise this. Similarly fe80::/10
+// (RFC 4291) are reserved for 'site-local' usage, but again this is not
+// implemented in the go libraries. Any developer who needs to distinguish
+// these cases will need to write their own routines to test for them.
+//
+// When using the go net package it is important to remember that IPv6
+// addresses subsume IPv4 and hence in many cases the same internal
+// representation is used for both, thus testing for the length of the IP
+// address is unreliable. The reliable test is to use the net.To4() which
+// will return a non-nil result if can be used as an IPv4 one. Any address
+// can be used as an IPv6 and hence the only reliable way to test for an IPv6
+// address that is not an IPv4 one also is for the To4 call to return nil for
+// it.
+package netstate
+
+import (
+	"fmt"
+	"net"
+	"strings"
+
+	"v.io/v23/ipc"
+
+	"v.io/x/ref/lib/netconfig"
+)
+
+// AddrIfc represents a network address and the network interface that
+// hosts it.
+type AddrIfc struct {
+	// Network address
+	Addr net.Addr
+
+	// The name of the network interface this address is hosted on, empty
+	// if this information is not available.
+	Name string
+
+	// The IPRoutes of the network interface this address is hosted on,
+	// nil if this information is not available.
+	IPRoutes []*netconfig.IPRoute
+}
+
+func (a *AddrIfc) String() string {
+	if a.IPRoutes != nil {
+		r := fmt.Sprintf("%s: %s[", a.Addr, a.Name)
+		for _, rt := range a.IPRoutes {
+			src := ""
+			if rt.PreferredSource != nil {
+				src = ", src: " + rt.PreferredSource.String()
+			}
+			r += fmt.Sprintf("{%d: net: %s, gw: %s%s}, ", rt.IfcIndex, rt.Net, rt.Gateway, src)
+		}
+		r = strings.TrimSuffix(r, ", ")
+		r += "]"
+		return r
+	}
+	return a.Addr.String()
+}
+
+func (a *AddrIfc) Address() net.Addr {
+	return a.Addr
+}
+
+func (a *AddrIfc) InterfaceIndex() int {
+	if len(a.IPRoutes) == 0 {
+		return -1
+	}
+	return a.IPRoutes[0].IfcIndex
+}
+
+func (a *AddrIfc) InterfaceName() string {
+	return a.Name
+}
+
+func (a *AddrIfc) Networks() []net.Addr {
+	nets := []net.Addr{}
+	for _, r := range a.IPRoutes {
+		nets = append(nets, &r.Net)
+	}
+	return nets
+}
+
+type AddrList []ipc.Address
+
+func (al AddrList) String() string {
+	r := ""
+	for _, v := range al {
+		r += fmt.Sprintf("(%s) ", v)
+	}
+	return strings.TrimRight(r, " ")
+}
+
+// GetAll gets all of the available addresses on the device, including
+// loopback addresses, non-IP protocols etc.
+func GetAll() (AddrList, error) {
+	interfaces, err := net.Interfaces()
+	if err != nil {
+		return nil, err
+	}
+	routes := netconfig.GetIPRoutes(false)
+	routeTable := make(map[int][]*netconfig.IPRoute)
+	for _, r := range routes {
+		routeTable[r.IfcIndex] = append(routeTable[r.IfcIndex], r)
+	}
+	var all AddrList
+	for _, ifc := range interfaces {
+		addrs, err := ifc.Addrs()
+		if err != nil {
+			continue
+		}
+		for _, a := range addrs {
+			all = append(all, &AddrIfc{a, ifc.Name, routeTable[ifc.Index]})
+		}
+	}
+	return all, nil
+}
+
+// GetAccessibleIPs returns all of the accessible IP addresses on the device
+// - i.e. excluding loopback and unspecified addresses.
+// The IP addresses returned will be host addresses.
+func GetAccessibleIPs() (AddrList, error) {
+	all, err := GetAll()
+	if err != nil {
+		return nil, err
+	}
+	return all.Map(ConvertAccessibleIPHost), nil
+}
+
+// AddressPredicate defines the function signature for predicate functions
+// to be used with AddrList
+type AddressPredicate func(a ipc.Address) bool
+
+// Filter returns all of the addresses for which the predicate
+// function is true.
+func (al AddrList) Filter(predicate AddressPredicate) AddrList {
+	r := AddrList{}
+	for _, a := range al {
+		if predicate(a) {
+			r = append(r, a)
+		}
+	}
+	return r
+}
+
+type Mapper func(a ipc.Address) ipc.Address
+
+// Map will apply the Mapper function to all of the items in its receiver
+// and return a new AddrList containing all of the non-nil results from
+// said calls.
+func (al AddrList) Map(mapper Mapper) AddrList {
+	var ral AddrList
+	for _, a := range al {
+		if na := mapper(a); na != nil {
+			ral = append(ral, na)
+		}
+	}
+	return ral
+}
+
+// ConvertToIPHost converts the network address component of an ipc.Address into
+// an instance with a net.Addr that contains an IP host address (as opposed to a
+// network CIDR for example).
+func ConvertToIPHost(a ipc.Address) ipc.Address {
+	aifc, ok := a.(*AddrIfc)
+	if !ok {
+		return nil
+	}
+	aifc.Addr = AsIPAddr(aifc.Addr)
+	return aifc
+}
+
+// ConvertAccessibleIPHost converts the network address component of an ipc.Address
+// into an instance with a net.Addr that contains an IP host address (as opposed to a
+// network CIDR for example) with filtering out a loopback or non-accessible IPs.
+func ConvertAccessibleIPHost(a ipc.Address) ipc.Address {
+	if !IsAccessibleIP(a) {
+		return nil
+	}
+	aifc, ok := a.(*AddrIfc)
+	if !ok {
+		return nil
+	}
+	if ip := AsIPAddr(aifc.Addr); ip != nil {
+		aifc.Addr = ip
+	}
+	return aifc
+}
+
+// IsIPProtocol returns true if its parameter is one of the allowed
+// network/protocol values for IP.
+func IsIPProtocol(n string) bool {
+	// Removed the training IP version number.
+	n = strings.TrimRightFunc(n, func(r rune) bool { return r == '4' || r == '6' })
+	switch n {
+	case "ip+net", "ip", "tcp", "udp", "ws", "wsh":
+		return true
+	default:
+		return false
+	}
+}
+
+// AsIPAddr returns its argument as a net.IPAddr if that's possible.
+func AsIPAddr(a net.Addr) *net.IPAddr {
+	if v, ok := a.(*net.IPAddr); ok {
+		return v
+	}
+	if ipn, ok := a.(*net.IPNet); ok {
+		return &net.IPAddr{IP: ipn.IP}
+	}
+	if IsIPProtocol(a.Network()) {
+		if r := net.ParseIP(a.String()); r != nil {
+			return &net.IPAddr{IP: r}
+		}
+	}
+	return nil
+}
+
+// AsIP returns its argument as a net.IP if that's possible.
+func AsIP(a net.Addr) net.IP {
+	ipAddr := AsIPAddr(a)
+	if ipAddr == nil {
+		return nil
+	}
+	return ipAddr.IP
+}
+
+// IsUnspecified returns true if its argument is an unspecified IP address
+func IsUnspecifiedIP(a ipc.Address) bool {
+	if ip := AsIP(a.Address()); ip != nil {
+		return ip.IsUnspecified()
+	}
+	return false
+}
+
+// IsLoopback returns true if its argument is a loopback IP address
+func IsLoopbackIP(a ipc.Address) bool {
+	if ip := AsIP(a.Address()); ip != nil && !ip.IsUnspecified() {
+		return ip.IsLoopback()
+	}
+	return false
+}
+
+// IsAccessible returns true if its argument is an accessible (non-loopback)
+// IP address.
+func IsAccessibleIP(a ipc.Address) bool {
+	if ip := AsIP(a.Address()); ip != nil && !ip.IsUnspecified() {
+		return !ip.IsLoopback()
+	}
+	return false
+}
+
+// IsUnicastIP returns true if its argument is a unicast IP address.
+func IsUnicastIP(a ipc.Address) bool {
+	if ip := AsIP(a.Address()); ip != nil && !ip.IsUnspecified() {
+		// ipv4 or v6
+		return !(ip.IsMulticast() || ip.IsLinkLocalMulticast() || ip.IsInterfaceLocalMulticast())
+	}
+	return false
+}
+
+// IsUnicastIPv4 returns true if its argument is a unicast IP4 address
+func IsUnicastIPv4(a ipc.Address) bool {
+	if ip := AsIP(a.Address()); ip != nil && ip.To4() != nil {
+		return !ip.IsUnspecified() && !ip.IsMulticast()
+	}
+	return false
+}
+
+// IsPublicUnicastIPv4 returns true if its argument is a globally routable,
+// public IPv4 unicast address.
+func IsPublicUnicastIPv4(a ipc.Address) bool {
+	if ip := AsIP(a.Address()); ip != nil && !ip.IsUnspecified() {
+		if t := ip.To4(); t != nil && IsGloballyRoutableIP(t) {
+			return !ip.IsMulticast()
+		}
+	}
+	return false
+}
+
+// IsUnicastIPv6 returns true if its argument is a unicast IPv6 address
+func IsUnicastIPv6(a ipc.Address) bool {
+	if ip := AsIP(a.Address()); ip != nil && ip.To4() == nil {
+		return !ip.IsUnspecified() && !(ip.IsLinkLocalMulticast() || ip.IsInterfaceLocalMulticast())
+	}
+	return false
+}
+
+// IsUnicastIPv6 returns true if its argument is a globally routable IP6
+// address
+func IsPublicUnicastIPv6(a ipc.Address) bool {
+	if ip := AsIP(a.Address()); ip != nil && ip.To4() == nil {
+		if t := ip.To16(); t != nil && IsGloballyRoutableIP(t) {
+			return true
+		}
+	}
+	return false
+}
+
+// IsPublicUnicastIP returns true if its argument is a global routable IPv4
+// or 6 address.
+func IsPublicUnicastIP(a ipc.Address) bool {
+	if ip := AsIP(a.Address()); ip != nil {
+		if t := ip.To4(); t != nil && IsGloballyRoutableIP(t) {
+			return true
+		}
+		if t := ip.To16(); t != nil && IsGloballyRoutableIP(t) {
+			return true
+		}
+	}
+	return false
+}
+
+func diffAB(a, b AddrList) AddrList {
+	diff := AddrList{}
+	for _, av := range a {
+		found := false
+		for _, bv := range b {
+			if av.Address().Network() == bv.Address().Network() &&
+				av.Address().String() == bv.Address().String() {
+				found = true
+				break
+			}
+		}
+		if !found {
+			diff = append(diff, av)
+		}
+	}
+	return diff
+}
+
+// FindAdded returns the set addresses that are present in b, but not
+// in a - i.e. have been added.
+func FindAdded(a, b AddrList) AddrList {
+	return diffAB(b, a)
+}
+
+// FindRemoved returns the set of addresses that are present in a, but not
+// in b - i.e. have been removed.
+func FindRemoved(a, b AddrList) AddrList {
+	return diffAB(a, b)
+}
+
+// SameMachine returns true if the provided addr is on the device executing this
+// function.
+func SameMachine(addr net.Addr) (bool, error) {
+	// The available interfaces may change between calls.
+	addrs, err := GetAll()
+	if err != nil {
+		return false, err
+	}
+	ips := make(map[string]struct{})
+	for _, a := range addrs {
+		ip, _, err := net.ParseCIDR(a.Address().String())
+		if err != nil {
+			return false, err
+		}
+		ips[ip.String()] = struct{}{}
+	}
+
+	client, _, err := net.SplitHostPort(addr.String())
+	if err != nil {
+		return false, err
+	}
+	_, islocal := ips[client]
+	return islocal, nil
+}
diff --git a/netstate/netstate_test.go b/netstate/netstate_test.go
new file mode 100644
index 0000000..954b9d4
--- /dev/null
+++ b/netstate/netstate_test.go
@@ -0,0 +1,448 @@
+package netstate_test
+
+import (
+	"net"
+	"reflect"
+	"testing"
+
+	"v.io/v23/ipc"
+
+	"v.io/x/ref/lib/netconfig"
+	"v.io/x/ref/lib/netstate"
+)
+
+func TestGet(t *testing.T) {
+	// We assume that this machine running this test has at least
+	// one non-loopback interface.
+	all, err := netstate.GetAll()
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	all = all.Map(netstate.ConvertToIPHost)
+	accessible, err := netstate.GetAccessibleIPs()
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	if len(all) == 0 || len(accessible) == 0 {
+		t.Errorf("expected non zero lengths, not %d and %d", len(all), len(accessible))
+	}
+	if len(accessible) > len(all) {
+		t.Errorf("should never be more accessible addresses than 'all' addresses")
+	}
+	loopback := netstate.FindAdded(accessible, all)
+	if got, want := loopback.Filter(netstate.IsLoopbackIP), loopback; !reflect.DeepEqual(got, want) {
+		t.Errorf("got %v, want %v", got, want)
+	}
+}
+
+type ma struct {
+	n, a string
+}
+
+func (a *ma) Network() string {
+	return a.n
+}
+
+func (a *ma) String() string {
+	return a.a
+}
+
+func mkAddr(n, a string) net.Addr {
+	ip := net.ParseIP(a)
+	return &ma{n: n, a: ip.String()}
+}
+
+func TestAsIP(t *testing.T) {
+	lh := net.ParseIP("127.0.0.1")
+	if got, want := netstate.AsIP(&net.IPAddr{IP: lh}), "127.0.0.1"; got == nil || got.String() != want {
+		t.Errorf("got %v, want %v", got, want)
+	}
+	if got, want := netstate.AsIP(&net.IPNet{IP: lh}), "127.0.0.1"; got == nil || got.String() != want {
+		t.Errorf("got %v, want %v", got, want)
+	}
+	if got, want := netstate.AsIP(&ma{"tcp", lh.String()}), "127.0.0.1"; got == nil || got.String() != want {
+		t.Errorf("got %v, want %v", got, want)
+	}
+}
+
+func TestRoutes(t *testing.T) {
+	accessible, err := netstate.GetAccessibleIPs()
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	ifcl, err := netstate.GetInterfaces()
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+
+	if len(ifcl) == 0 || len(accessible) == 0 {
+		t.Errorf("expected non zero lengths, not %d and %d", len(ifcl), len(accessible))
+	}
+
+	routes := netstate.GetRoutes()
+	// Make sure that the routes refer to valid interfaces
+	for _, r := range routes {
+		found := false
+		for _, ifc := range ifcl {
+			if r.IfcIndex == ifc.Index {
+				found = true
+				break
+			}
+		}
+		if !found {
+			t.Errorf("failed to find ifc index %d", r.IfcIndex)
+		}
+	}
+}
+
+func mkAddrIfc(n, a string) *netstate.AddrIfc {
+	ip := net.ParseIP(a)
+	return &netstate.AddrIfc{
+		Addr: &ma{n: n, a: ip.String()},
+	}
+}
+
+type hw struct{}
+
+func (*hw) Network() string { return "mac" }
+func (*hw) String() string  { return "01:23:45:67:89:ab:cd:ef" }
+
+func TestPredicates(t *testing.T) {
+	hwifc := &netstate.AddrIfc{Addr: &hw{}}
+	if got, want := netstate.IsUnicastIP(hwifc), false; got != want {
+		t.Errorf("got %t, want %t", got, want)
+
+	}
+	cases := []struct {
+		f func(a ipc.Address) bool
+		a string
+		r bool
+	}{
+		{netstate.IsUnspecifiedIP, "0.0.0.0", true},
+		{netstate.IsUnspecifiedIP, "::", true},
+		{netstate.IsUnspecifiedIP, "127.0.0.1", false},
+		{netstate.IsUnspecifiedIP, "::1", false},
+
+		{netstate.IsLoopbackIP, "0.0.0.0", false},
+		{netstate.IsLoopbackIP, "::", false},
+		{netstate.IsLoopbackIP, "127.0.0.1", true},
+		{netstate.IsLoopbackIP, "::1", true},
+
+		{netstate.IsAccessibleIP, "0.0.0.0", false},
+		{netstate.IsAccessibleIP, "::", false},
+		{netstate.IsAccessibleIP, "127.0.0.1", false},
+		{netstate.IsAccessibleIP, "::1", false},
+		{netstate.IsAccessibleIP, "224.0.0.2", true},
+		{netstate.IsAccessibleIP, "fc00:1234::", true},
+		{netstate.IsAccessibleIP, "192.168.1.1", true},
+		{netstate.IsAccessibleIP, "2001:4860:0:2001::68", true},
+
+		{netstate.IsUnicastIP, "0.0.0.0", false},
+		{netstate.IsUnicastIP, "::", false},
+		{netstate.IsUnicastIP, "127.0.0.1", true},
+		{netstate.IsUnicastIP, "::1", true},
+		{netstate.IsUnicastIP, "192.168.1.2", true},
+		{netstate.IsUnicastIP, "74.125.239.36", true},
+		{netstate.IsUnicastIP, "224.0.0.2", false},
+		{netstate.IsUnicastIP, "fc00:1235::", true},
+		{netstate.IsUnicastIP, "ff01::01", false},
+		{netstate.IsUnicastIP, "2001:4860:0:2001::69", true},
+
+		{netstate.IsUnicastIPv4, "0.0.0.0", false},
+		{netstate.IsUnicastIPv4, "::", false},
+		{netstate.IsUnicastIPv4, "127.0.0.1", true},
+		{netstate.IsUnicastIPv4, "::1", false},
+		{netstate.IsUnicastIPv4, "192.168.1.3", true},
+		{netstate.IsUnicastIPv6, "74.125.239.37", false},
+		{netstate.IsUnicastIPv4, "224.0.0.2", false},
+		{netstate.IsUnicastIPv4, "fc00:1236::", false},
+		{netstate.IsUnicastIPv4, "ff01::02", false},
+		{netstate.IsUnicastIPv4, "2001:4860:0:2001::6a", false},
+
+		{netstate.IsUnicastIPv6, "0.0.0.0", false},
+		{netstate.IsUnicastIPv6, "::", false},
+		{netstate.IsUnicastIPv6, "127.0.0.1", false},
+		{netstate.IsUnicastIPv6, "::1", true},
+		{netstate.IsUnicastIPv6, "192.168.1.4", false},
+		{netstate.IsUnicastIPv6, "74.125.239.38", false},
+		{netstate.IsUnicastIPv6, "224.0.0.2", false},
+		{netstate.IsUnicastIPv6, "fc00:1237::", true},
+		{netstate.IsUnicastIPv6, "ff01::03", false},
+		{netstate.IsUnicastIPv6, "2607:f8b0:4003:c00::6b", true},
+
+		{netstate.IsPublicUnicastIP, "0.0.0.0", false},
+		{netstate.IsPublicUnicastIP, "::", false},
+		{netstate.IsPublicUnicastIP, "127.0.0.1", false},
+		{netstate.IsPublicUnicastIP, "::1", false},
+		{netstate.IsPublicUnicastIP, "192.168.1.2", false},
+		{netstate.IsPublicUnicastIP, "74.125.239.39", true},
+		{netstate.IsPublicUnicastIP, "224.0.0.2", false},
+		// Arguably this is buggy, the fc00:/7 prefix is supposed to be
+		// non-routable.
+		{netstate.IsPublicUnicastIP, "fc00:1238::", true},
+		{netstate.IsPublicUnicastIP, "ff01::01", false},
+		{netstate.IsPublicUnicastIP, "2001:4860:0:2001::69", true},
+
+		{netstate.IsPublicUnicastIPv4, "0.0.0.0", false},
+		{netstate.IsPublicUnicastIPv4, "::", false},
+		{netstate.IsPublicUnicastIPv4, "127.0.0.1", false},
+		{netstate.IsPublicUnicastIPv4, "::1", false},
+		{netstate.IsPublicUnicastIPv4, "192.168.1.3", false},
+		{netstate.IsPublicUnicastIPv4, "74.125.239.40", true},
+		{netstate.IsPublicUnicastIPv4, "224.0.0.2", false},
+		{netstate.IsPublicUnicastIPv4, "fc00:1239::", false},
+		{netstate.IsPublicUnicastIPv4, "ff01::02", false},
+		{netstate.IsPublicUnicastIPv4, "2001:4860:0:2001::6a", false},
+
+		{netstate.IsPublicUnicastIPv6, "0.0.0.0", false},
+		{netstate.IsPublicUnicastIPv6, "::", false},
+		{netstate.IsPublicUnicastIPv6, "127.0.0.1", false},
+		{netstate.IsPublicUnicastIPv6, "::1", false},
+		{netstate.IsPublicUnicastIPv6, "192.168.1.4", false},
+		{netstate.IsPublicUnicastIPv6, "74.125.239.41", false},
+		{netstate.IsPublicUnicastIPv6, "224.0.0.2", false},
+		// Arguably this is buggy, the fc00:/7 prefix is supposed to be
+		// non-routable.
+		{netstate.IsPublicUnicastIPv6, "fc00:123a::", true},
+		{netstate.IsPublicUnicastIPv6, "ff01::03", false},
+		{netstate.IsPublicUnicastIPv6, "2607:f8b0:4003:c00::6b", true},
+	}
+	for i, c := range cases {
+		net := "tcp"
+		if got, want := c.f(mkAddrIfc(net, c.a)), c.r; got != want {
+			t.Errorf("#%d: %s %s: got %t, want %t", i+1, net, c.a, got, want)
+		}
+	}
+}
+
+func TestRoutePredicate(t *testing.T) {
+	net1_ip, net1, _ := net.ParseCIDR("192.168.1.10/24")
+	net2_ip, net2, _ := net.ParseCIDR("172.16.1.11/24")
+	net3_ip, net3, _ := net.ParseCIDR("172.16.2.12/24")
+	// net4 and net5 are on the same interface.
+	net4_ip, net4, _ := net.ParseCIDR("172.19.39.142/23")
+	net5_ip, net5, _ := net.ParseCIDR("2620::1000:5e01:56e4:3aff:fef1:1383/64")
+
+	rt1 := []*netconfig.IPRoute{{*net1, net.ParseIP("192.168.1.1"), nil, 1}}
+	rt2 := []*netconfig.IPRoute{{*net2, net.ParseIP("172.16.1.1"), nil, 2}}
+	rt3 := []*netconfig.IPRoute{{*net3, net.ParseIP("172.16.2.1"), nil, 3}}
+	rt4_0 := &netconfig.IPRoute{*net4, net.ParseIP("172.19.39.142"), nil, 6}
+	rt4_1 := &netconfig.IPRoute{*net5, net.ParseIP("fe80::5:73ff:fea0:fb"), nil, 6}
+	rt4 := []*netconfig.IPRoute{rt4_0, rt4_1}
+
+	net1_addr := &netstate.AddrIfc{&net.IPAddr{IP: net1_ip}, "eth0", rt1}
+	net2_addr := &netstate.AddrIfc{&net.IPAddr{IP: net2_ip}, "eth1", rt2}
+	net3_addr := &netstate.AddrIfc{&net.IPAddr{IP: net3_ip}, "eth2", rt3}
+	net4_addr := &netstate.AddrIfc{&net.IPAddr{IP: net4_ip}, "wn0", rt4}
+	net5_addr := &netstate.AddrIfc{&net.IPAddr{IP: net5_ip}, "wn0", rt4}
+
+	al := netstate.AddrList{}
+	if got, want := al.Filter(netstate.IsOnDefaultRoute), (netstate.AddrList{}); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %v, want %v", got, want)
+	}
+
+	al = netstate.AddrList{net1_addr, net2_addr, net3_addr, net4_addr, net5_addr}
+	if got, want := al.Filter(netstate.IsOnDefaultRoute), (netstate.AddrList{}); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %v, want %v", got, want)
+	}
+
+	defaultRoute := net.IPNet{net.IPv4zero, make([]byte, net.IPv4len)}
+	// Make eth1 a default route.
+	rt2[0].Net = defaultRoute
+	if got, want := al.Filter(netstate.IsOnDefaultRoute), (netstate.AddrList{net2_addr}); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %v, want %v", got, want)
+	}
+
+	// Make wn0 a default route also.
+	rt3[0].Net = defaultRoute
+	if got, want := al.Filter(netstate.IsOnDefaultRoute), (netstate.AddrList{net2_addr, net3_addr}); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %v, want %v", got, want)
+	}
+
+	// Restore the original route.
+	rt2[0].Net = *net2
+	rt4_0.Net = defaultRoute
+	if got, want := al.Filter(netstate.IsOnDefaultRoute), (netstate.AddrList{net3_addr, net4_addr}); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %v, want %v", got, want)
+	}
+
+	// Shouldn't return the IPv6 default route so long as al doesn't
+	// contain any IPv6 default routes.
+	rt4_0.Net = *net4
+	rt4_1.Net = defaultRoute
+	if got, want := al.Filter(netstate.IsOnDefaultRoute), (netstate.AddrList{net3_addr}); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %v, want %v", got, want)
+	}
+
+	// Now that we have an IPv6 default route that matches an IPv6 gateway
+	// we can expect to find the IPv6 host address
+	rt4_1.Net = net.IPNet{net.IPv6zero, make([]byte, net.IPv6len)}
+	if got, want := al.Filter(netstate.IsOnDefaultRoute), (netstate.AddrList{net3_addr, net5_addr}); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %v, want %v", got, want)
+	}
+}
+
+var (
+	a  = mkAddrIfc("tcp4", "1.2.3.4")
+	b  = mkAddrIfc("tcp4", "1.2.3.5")
+	c  = mkAddrIfc("tcp4", "1.2.3.6")
+	d  = mkAddrIfc("tcp4", "1.2.3.7")
+	a6 = mkAddrIfc("tcp6", "2001:4860:0:2001::68")
+	b6 = mkAddrIfc("tcp6", "2001:4860:0:2001::69")
+	c6 = mkAddrIfc("tcp6", "2001:4860:0:2001::70")
+	d6 = mkAddrIfc("tcp6", "2001:4860:0:2001::71")
+)
+
+func TestRemoved(t *testing.T) {
+	al := netstate.AddrList{a, b, c, a6, b6, c6}
+	bl := netstate.AddrList{}
+
+	// no changes.
+	got, want := netstate.FindRemoved(al, al), netstate.AddrList{}
+	if !reflect.DeepEqual(got, want) {
+		t.Errorf("got %#v, want %#v", got, want)
+	}
+
+	// missing everything
+	if got, want := netstate.FindRemoved(al, bl), al; !reflect.DeepEqual(got, want) {
+		t.Errorf("got %s, want %s", got, want)
+	}
+
+	// missing nothing
+	if got, want := netstate.FindRemoved(bl, al), (netstate.AddrList{}); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %s, want %s", got, want)
+	}
+
+	// remove some addresses
+	bl = netstate.AddrList{a, b, a6, b6}
+	if got, want := netstate.FindRemoved(al, bl), (netstate.AddrList{c, c6}); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %s, want %s", got, want)
+	}
+
+	// add some addresses
+	bl = netstate.AddrList{a, b, c, a6, b6, c6, d6}
+	if got, want := netstate.FindRemoved(al, bl), (netstate.AddrList{}); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %s, want %s", got, want)
+	}
+
+	// change some addresses
+	bl = netstate.AddrList{a, b, d, a6, d6, c6}
+	if got, want := netstate.FindRemoved(al, bl), (netstate.AddrList{c, b6}); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %s, want %s", got, want)
+	}
+}
+
+func TestAdded(t *testing.T) {
+	al := netstate.AddrList{a, b, c, a6, b6, c6}
+	bl := netstate.AddrList{}
+
+	// no changes.
+	if got, want := netstate.FindAdded(al, al), (netstate.AddrList{}); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %s, want %s", got, want)
+	}
+
+	// add nothing
+	if got, want := netstate.FindAdded(al, bl), bl; !reflect.DeepEqual(got, want) {
+		t.Errorf("got %s, want %s", got, want)
+	}
+
+	// add everything
+	if got, want := netstate.FindAdded(bl, al), al; !reflect.DeepEqual(got, want) {
+		t.Errorf("got %s, want %s", got, want)
+	}
+
+	// add some addresses
+	bl = netstate.AddrList{a, b, c, d, a6, b6, c6, d6}
+	if got, want := netstate.FindAdded(al, bl), (netstate.AddrList{d, d6}); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %s, want %s", got, want)
+	}
+
+	// remove some addresses
+	bl = netstate.AddrList{a, b, c, b6}
+	if got, want := netstate.FindAdded(al, bl), (netstate.AddrList{}); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %s, want %s", got, want)
+	}
+
+	// change some addresses
+	bl = netstate.AddrList{a, d, c, a6, d6, c6}
+	if got, want := netstate.FindAdded(al, bl), (netstate.AddrList{d, d6}); !reflect.DeepEqual(got, want) {
+		t.Errorf("got %s, want %s", got, want)
+	}
+}
+
+// buildNonLocalhostTestAddress constructs a selection of test addresses
+// that are local.
+func buildNonLocalhostTestAddress(t *testing.T) []string {
+	addrs, err := net.InterfaceAddrs()
+	if err != nil {
+		t.Errorf("InterfaceAddrs() failed: %v\n", err)
+	}
+
+	ips := make([]string, 0, len(addrs))
+	for _, a := range addrs {
+		ip, _, err := net.ParseCIDR(a.String())
+		if err != nil {
+			t.Errorf("ParseCIDR() failed: %v\n", err)
+		}
+		ips = append(ips, net.JoinHostPort(ip.String(), "111"))
+	}
+	return ips
+}
+
+func TestSameMachine(t *testing.T) {
+	cases := []struct {
+		Addr *ma
+		Same bool
+		Err  error
+	}{
+		{
+			Addr: &ma{
+				n: "tcp",
+				a: "batman.com:4444",
+			},
+			Same: false,
+			Err:  nil,
+		},
+		{
+			Addr: &ma{
+				n: "tcp",
+				a: "127.0.0.1:1000",
+			},
+			Same: true,
+			Err:  nil,
+		},
+		{
+			Addr: &ma{
+				n: "tcp",
+				a: "::1/128",
+			},
+			Same: false,
+			Err:  &net.AddrError{Err: "too many colons in address", Addr: "::1/128"},
+		},
+	}
+
+	for _, a := range buildNonLocalhostTestAddress(t) {
+		cases = append(cases, struct {
+			Addr *ma
+			Same bool
+			Err  error
+		}{
+			Addr: &ma{
+				n: "tcp",
+				a: a,
+			},
+			Same: true,
+			Err:  nil,
+		})
+	}
+
+	for _, v := range cases {
+		issame, err := netstate.SameMachine(v.Addr)
+		if !reflect.DeepEqual(err, v.Err) {
+			t.Errorf("Bad error: got %#v, expected %#v\n", err, v.Err)
+		}
+		if issame != v.Same {
+			t.Errorf("for Endpoint address %v: got %v, expected %v\n", v.Addr.a, issame, v.Same)
+		}
+	}
+}
diff --git a/netstate/route.go b/netstate/route.go
new file mode 100644
index 0000000..906dc9e
--- /dev/null
+++ b/netstate/route.go
@@ -0,0 +1,105 @@
+package netstate
+
+import (
+	"fmt"
+	"net"
+	"strings"
+
+	"v.io/v23/ipc"
+
+	"v.io/x/ref/lib/netconfig"
+)
+
+// Interface represents a network interface.
+type Interface struct {
+	Index int
+	Name  string
+}
+type InterfaceList []*Interface
+
+// GetInterfaces returns a list of all of the network interfaces on this
+// device.
+func GetInterfaces() (InterfaceList, error) {
+	ifcl := InterfaceList{}
+	interfaces, err := net.Interfaces()
+	if err != nil {
+		return nil, err
+	}
+	for _, ifc := range interfaces {
+		ifcl = append(ifcl, &Interface{ifc.Index, ifc.Name})
+	}
+	return ifcl, nil
+}
+
+func (ifcl InterfaceList) String() string {
+	r := ""
+	for _, ifc := range ifcl {
+		r += fmt.Sprintf("(%d: %s) ", ifc.Index, ifc.Name)
+	}
+	return strings.TrimRight(r, " ")
+}
+
+// IPRouteList is a slice of IPRoutes as returned by the netconfig package.
+type IPRouteList []*netconfig.IPRoute
+
+func (rl IPRouteList) String() string {
+	r := ""
+	for _, rt := range rl {
+		src := ""
+		if len(rt.PreferredSource) > 0 {
+			src = ", src: " + rt.PreferredSource.String()
+		}
+		r += fmt.Sprintf("(%d: net: %s, gw: %s%s) ", rt.IfcIndex, rt.Net, rt.Gateway, src)
+	}
+	return strings.TrimRight(r, " ")
+}
+
+func GetRoutes() IPRouteList {
+	return netconfig.GetIPRoutes(false)
+}
+
+// RoutePredicate defines the function signature for predicate functions
+// to be used with RouteList
+type RoutePredicate func(r *netconfig.IPRoute) bool
+
+// Filter returns all of the routes for which the predicate
+// function is true.
+func (rl IPRouteList) Filter(predicate RoutePredicate) IPRouteList {
+	r := IPRouteList{}
+	for _, rt := range rl {
+		if predicate(rt) {
+			r = append(r, rt)
+		}
+	}
+	return r
+}
+
+// IsDefaultRoute returns true if the supplied IPRoute is a default route.
+func IsDefaultRoute(r *netconfig.IPRoute) bool {
+	return netconfig.IsDefaultIPRoute(r)
+}
+
+// IsOnDefaultRoute returns true for addresses that are on an interface that
+// has a default route set for the supplied address.
+func IsOnDefaultRoute(a ipc.Address) bool {
+	aifc, ok := a.(*AddrIfc)
+	if !ok || len(aifc.IPRoutes) == 0 {
+		return false
+	}
+	ipv4 := IsUnicastIPv4(a)
+	for _, r := range aifc.IPRoutes {
+		// Ignore entries with a nil gateway.
+		if r.Gateway == nil {
+			continue
+		}
+		// We have a default route, so we check the gateway to make sure
+		// it matches the format of the default route.
+		if ipv4 {
+			return netconfig.IsDefaultIPv4Route(r) && r.Gateway.To4() != nil
+		}
+		if netconfig.IsDefaultIPv6Route(r) {
+			return true
+		}
+	}
+	return false
+}
diff --git a/textutil/doc.go b/textutil/doc.go
new file mode 100644
index 0000000..fed643b
--- /dev/null
+++ b/textutil/doc.go
@@ -0,0 +1,8 @@
+// Package textutil implements utilities for handling human-readable text.
+//
+// This package includes a combination of low-level and high-level utilities.
+// The main high-level utilities are:
+//   NewUTF8LineWriter: Line-based text formatter.
+//   PrefixWriter:      Add prefix to output.
+//   ByteReplaceWriter: Replace single byte with bytes in output.
+package textutil
diff --git a/textutil/line_writer.go b/textutil/line_writer.go
new file mode 100644
index 0000000..c7df3a5
--- /dev/null
+++ b/textutil/line_writer.go
@@ -0,0 +1,445 @@
+package textutil
+
+import (
+	"fmt"
+	"io"
+	"unicode"
+)
+
+// LineWriter implements an io.Writer filter that formats input text into output
+// lines with a given target width in runes.
+//
+// Each input rune is classified into one of three kinds:
+//   EOL:    end-of-line, consisting of \f, \n, \r, \v, U+2028 or U+2029
+//   Space:  defined by unicode.IsSpace
+//   Letter: everything else
+//
+// The input text is expected to consist of words, defined as sequences of
+// letters.  Sequences of words form paragraphs, where paragraphs are separated
+// by either blank lines (that contain no letters), or an explicit U+2029
+// ParagraphSeparator.  Input lines with leading spaces are treated verbatim.
+//
+// Paragraphs are output as word-wrapped lines; line breaks only occur at word
+// boundaries.  Output lines are usually no longer than the target width.  The
+// exceptions are single words longer than the target width, which are output on
+// their own line, and verbatim lines, which may be arbitrarily longer or
+// shorter than the width.
+//
+// Output lines never contain trailing spaces.  Only verbatim output lines may
+// contain leading spaces.  Spaces separating input words are output verbatim,
+// unless it would result in a line with leading or trailing spaces.
+//
+// EOL runes within the input text are never written to the output; the output
+// line terminator and paragraph separator may be configured, and some EOL may
+// be output as a single space ' ' to maintain word separation.
+//
+// The algorithm greedily fills each output line with as many words as it can,
+// assuming that all Unicode code points have the same width.  Invalid UTF-8 is
+// silently transformed to the replacement character U+FFFD and treated as a
+// single rune.
+//
+// Flush must be called after the last call to Write; the input is buffered.
+//
+//   Implementation note: line breaking is a complicated topic.  This approach
+//   attempts to be simple and useful; a full implementation conforming to
+//   Unicode Standard Annex #14 would be complicated, and is not implemented.
+//   Languages that don't use spaces to separate words (e.g. CJK) won't work
+//   well under the current approach.
+//
+//   http://www.unicode.org/reports/tr14 [Unicode Line Breaking Algorithm]
+//   http://www.unicode.org/versions/Unicode4.0.0/ch05.pdf [5.8 Newline Guidelines]
+type LineWriter struct {
+	// State configured by the user.
+	w            io.Writer
+	runeDecoder  RuneChunkDecoder
+	width        runePos
+	lineTerm     []byte
+	paragraphSep string
+	indents      []string
+
+	// The buffer contains a single output line.
+	lineBuf byteRuneBuffer
+
+	// Keep track of the previous state and rune.
+	prevState state
+	prevRune  rune
+
+	// Keep track of blank input lines.
+	inputLineHasLetter bool
+
+	// lineBuf positions where the line starts (after separators and indents), a
+	// new word has started and the last word has ended.
+	lineStart    bytePos
+	newWordStart bytePos
+	lastWordEnd  bytePos
+
+	// Keep track of paragraph terminations and line indices, so we can output the
+	// paragraph separator and indents correctly.
+	terminateParagraph bool
+	paragraphLineIndex int
+	wroteFirstLine     bool
+}
+
+type state int
+
+const (
+	stateWordWrap  state = iota // Perform word-wrapping [start state]
+	stateVerbatim               // Verbatim output-line, no word-wrapping
+	stateSkipSpace              // Skip spaces in input line.
+)
+
+// NewLineWriter returns a new LineWriter with the given target width in runes,
+// producing output on the underlying writer w.  The dec and enc are used to
+// respectively decode runes from Write calls, and encode runes to w.
+func NewLineWriter(w io.Writer, width int, dec RuneChunkDecoder, enc RuneEncoder) *LineWriter {
+	ret := &LineWriter{
+		w:            w,
+		runeDecoder:  dec,
+		width:        runePos(width),
+		lineTerm:     []byte("\n"),
+		paragraphSep: "\n",
+		prevState:    stateWordWrap,
+		prevRune:     LineSeparator,
+		lineBuf:      byteRuneBuffer{enc: enc},
+	}
+	ret.resetLine()
+	return ret
+}
+
+// NewUTF8LineWriter returns a new LineWriter filter that implements io.Writer,
+// and decodes and encodes runes in UTF-8.
+func NewUTF8LineWriter(w io.Writer, width int) *LineWriter {
+	return NewLineWriter(w, width, &UTF8ChunkDecoder{}, UTF8Encoder{})
+}
+
+// Width returns the target width in runes.  If width < 0 the width is
+// unlimited; each paragraph is output as a single line.
+func (w *LineWriter) Width() int { return int(w.width) }
+
+// SetLineTerminator sets the line terminator for subsequent Write calls.  Every
+// output line is terminated with term; EOL runes from the input are never
+// written to the output.  A new LineWriter instance uses "\n" as the default
+// line terminator.
+//
+// Calls Flush internally, and returns any Flush error.
+func (w *LineWriter) SetLineTerminator(term string) error {
+	if err := w.Flush(); err != nil {
+		return err
+	}
+	w.lineTerm = []byte(term)
+	w.resetLine()
+	return nil
+}
+
+// SetParagraphSeparator sets the paragraph separator for subsequent Write
+// calls.  Every consecutive pair of non-empty paragraphs is separated with sep;
+// EOL runes from the input are never written to the output.  A new LineWriter
+// instance uses "\n" as the default paragraph separator.
+//
+// Calls Flush internally, and returns any Flush error.
+func (w *LineWriter) SetParagraphSeparator(sep string) error {
+	if err := w.Flush(); err != nil {
+		return err
+	}
+	w.paragraphSep = sep
+	w.resetLine()
+	return nil
+}
+
+// SetIndents sets the indentation for subsequent Write calls.  Multiple indents
+// may be set, corresponding to the indent to use for the corresponding
+// paragraph line.  E.g. SetIndents("AA", "BBB", C") means the first line in
+// each paragraph is indented with "AA", the second line in each paragraph is
+// indented with "BBB", and all subsequent lines in each paragraph are indented
+// with "C".
+//
+// SetIndents() is equivalent to SetIndents(""), SetIndents("", ""), etc.
+//
+// A new LineWriter instance has no indents by default.
+//
+// Calls Flush internally, and returns any Flush error.
+func (w *LineWriter) SetIndents(indents ...string) error {
+	if err := w.Flush(); err != nil {
+		return err
+	}
+	// Copy indents in case the user passed the slice via SetIndents(p...), and
+	// canonicalize the all empty case to nil.
+	allEmpty := true
+	w.indents = make([]string, len(indents))
+	for ix, indent := range indents {
+		w.indents[ix] = indent
+		if indent != "" {
+			allEmpty = false
+		}
+	}
+	if allEmpty {
+		w.indents = nil
+	}
+	w.resetLine()
+	return nil
+}
+
+// Write implements io.Writer by buffering data into the LineWriter w.  Actual
+// writes to the underlying writer may occur, and may include data buffered in
+// either this Write call or previous Write calls.
+//
+// Flush must be called after the last call to Write.
+func (w *LineWriter) Write(data []byte) (int, error) {
+	return RuneChunkWrite(w.runeDecoder, w.addRune, data)
+}
+
+// Flush flushes any remaining buffered text, and resets the paragraph line
+// count back to 0, so that indents will be applied starting from the first
+// line.  It does not imply a paragraph separator; repeated calls to Flush with
+// no intervening calls to other methods is equivalent to a single Flush.
+//
+// Flush must be called after the last call to Write, and may be called an
+// arbitrary number of times before the last Write.
+func (w *LineWriter) Flush() error {
+	if err := RuneChunkFlush(w.runeDecoder, w.addRune); err != nil {
+		return err
+	}
+	// Add U+2028 to force the last line (if any) to be written.
+	if err := w.addRune(LineSeparator); err != nil {
+		return err
+	}
+	// Reset the paragraph line count.
+	w.paragraphLineIndex = 0
+	w.resetLine()
+	return nil
+}
+
+// addRune is called every time w.runeDecoder decodes a full rune.
+func (w *LineWriter) addRune(r rune) error {
+	state, lineBreak := w.nextState(r, w.updateRune(r))
+	if lineBreak {
+		if err := w.writeLine(); err != nil {
+			return err
+		}
+	}
+	w.bufferRune(r, state, lineBreak)
+	w.prevState = state
+	w.prevRune = r
+	return nil
+}
+
+// We classify each incoming rune into three kinds for easier handling.
+type kind int
+
+const (
+	kindEOL kind = iota
+	kindSpace
+	kindLetter
+)
+
+func runeKind(r rune) kind {
+	switch r {
+	case '\f', '\n', '\r', '\v', LineSeparator, ParagraphSeparator:
+		return kindEOL
+	}
+	if unicode.IsSpace(r) {
+		return kindSpace
+	}
+	return kindLetter
+}
+
+func (w *LineWriter) updateRune(r rune) bool {
+	forceLineBreak := false
+	switch kind := runeKind(r); kind {
+	case kindEOL:
+		// Update lastWordEnd if the last word just ended.
+		if w.newWordStart != -1 {
+			w.newWordStart = -1
+			w.lastWordEnd = w.lineBuf.ByteLen()
+		}
+		switch {
+		case w.prevRune == '\r' && r == '\n':
+			// Treat "\r\n" as a single EOL; we've already handled the logic for '\r',
+			// so there's nothing to do when we see '\n'.
+		case r == LineSeparator:
+			// Treat U+2028 as a pure line break; it's never a paragraph break.
+			forceLineBreak = true
+		case r == ParagraphSeparator || !w.inputLineHasLetter:
+			// The paragraph has just been terminated if we see an explicit U+2029, or
+			// if we see a blank line, which may contain spaces.
+			forceLineBreak = true
+			w.terminateParagraph = true
+		}
+		w.inputLineHasLetter = false
+	case kindSpace:
+		// Update lastWordEnd if the last word just ended.
+		if w.newWordStart != -1 {
+			w.newWordStart = -1
+			w.lastWordEnd = w.lineBuf.ByteLen()
+		}
+	case kindLetter:
+		// Update newWordStart if a new word just started.
+		if w.newWordStart == -1 {
+			w.newWordStart = w.lineBuf.ByteLen()
+		}
+		w.inputLineHasLetter = true
+		w.terminateParagraph = false
+	default:
+		panic(fmt.Errorf("textutil: updateRune unhandled kind %d", kind))
+	}
+	return forceLineBreak
+}
+
+// nextState returns the next state and whether we should break the line.
+//
+// Here's a handy table that describes all the scenarios in which we will line
+// break input text, grouped by the reason for the break.  The current position
+// is the last non-* rune in each pattern, which is where we decide to break.
+//
+//              w.prevState   Next state   Buffer reset
+//              -----------   ----------   ------------
+//   ===== Force line break (U+2028 / U+2029, blank line) =====
+//   a..*|***   *             wordWrap     empty
+//   a._.|***   *             wordWrap     empty
+//   a+**|***   *             wordWrap     empty
+//
+//   ===== verbatim: wait for any EOL =====
+//   _*.*|***   verbatim      wordWrap     empty
+//
+//   ===== wordWrap: switch to verbatim =====
+//   a._*|***   wordWrap      verbatim     empty
+//
+//   ===== wordWrap: line is too wide =====
+//   abc.|***   wordWrap      wordWrap     empty
+//   abcd|.**   wordWrap      wordWrap     empty
+//   abcd|e.*   wordWrap      wordWrap     empty
+//   a_cd|.**   wordWrap      wordWrap     empty
+//
+//   abc_|***   wordWrap      skipSpace    empty
+//   abcd|_**   wordWrap      skipSpace    empty
+//   abcd|e_*   wordWrap      skipSpace    empty
+//   a_cd|_**   wordWrap      skipSpace    empty
+//
+//   a_cd|e**   wordWrap      start        newWordStart
+//
+//   LEGEND
+//     abcde  Letter
+//     .      End-of-line
+//     +      End-of-line (only U+2028 / U+2029)
+//     _      Space
+//     *      Any rune (letter, line-end or space)
+//     |      Visual indication of width=4, has no width itself.
+//
+// Note that Flush calls behave exactly as if an explicit U+2028 line separator
+// were added to the end of all buffered data.
+func (w *LineWriter) nextState(r rune, forceLineBreak bool) (state, bool) {
+	if forceLineBreak {
+		return stateWordWrap, true
+	}
+	kind := runeKind(r)
+	// Handle non word-wrap states, which are easy.
+	switch w.prevState {
+	case stateVerbatim:
+		if kind == kindEOL {
+			return stateWordWrap, true
+		}
+		return stateVerbatim, false
+	case stateSkipSpace:
+		if kind == kindSpace {
+			return stateSkipSpace, false
+		}
+		return stateWordWrap, false
+	}
+	// Handle stateWordWrap, which is more complicated.
+
+	// Switch to the verbatim state when we see a space right after an EOL.
+	if runeKind(w.prevRune) == kindEOL && kind == kindSpace {
+		return stateVerbatim, true
+	}
+	// Break on EOL or space when the line is too wide.  See above table.
+	if w.width >= 0 && w.width <= w.lineBuf.RuneLen()+1 {
+		switch kind {
+		case kindEOL:
+			return stateWordWrap, true
+		case kindSpace:
+			return stateSkipSpace, true
+		}
+		// case kindLetter falls through
+	}
+	// Handle the newWordStart case in the above table.
+	if w.width >= 0 && w.width < w.lineBuf.RuneLen()+1 && w.newWordStart != w.lineStart {
+		return stateWordWrap, true
+	}
+	// Stay in the wordWrap state and don't break the line.
+	return stateWordWrap, false
+}
+
+func (w *LineWriter) writeLine() error {
+	if w.lastWordEnd == -1 {
+		// Don't write blank lines, but we must reset the line in case the paragraph
+		// has just been terminated.
+		w.resetLine()
+		return nil
+	}
+	// Write the line (without trailing spaces) followed by the line terminator.
+	line := w.lineBuf.Bytes()[:w.lastWordEnd]
+	if _, err := w.w.Write(line); err != nil {
+		return err
+	}
+	if _, err := w.w.Write(w.lineTerm); err != nil {
+		return err
+	}
+	// Reset the line buffer.
+	w.wroteFirstLine = true
+	w.paragraphLineIndex++
+	if w.newWordStart != -1 {
+		// If we have an unterminated new word, we must be in the newWordStart case
+		// in the table above.  Handle the special buffer reset here.
+		newWord := string(w.lineBuf.Bytes()[w.newWordStart:])
+		w.resetLine()
+		w.newWordStart = w.lineBuf.ByteLen()
+		w.lineBuf.WriteString(newWord)
+	} else {
+		w.resetLine()
+	}
+	return nil
+}
+
+func (w *LineWriter) resetLine() {
+	w.lineBuf.Reset()
+	w.newWordStart = -1
+	w.lastWordEnd = -1
+	// Write the paragraph separator if the previous paragraph has terminated.
+	// This consumes no runes from the line width.
+	if w.wroteFirstLine && w.terminateParagraph {
+		w.lineBuf.WriteString0Runes(w.paragraphSep)
+		w.paragraphLineIndex = 0
+	}
+	// Add indent; a non-empty indent consumes runes from the line width.
+	var indent string
+	switch {
+	case w.paragraphLineIndex < len(w.indents):
+		indent = w.indents[w.paragraphLineIndex]
+	case len(w.indents) > 0:
+		indent = w.indents[len(w.indents)-1]
+	}
+	w.lineBuf.WriteString(indent)
+	w.lineStart = w.lineBuf.ByteLen()
+}
+
+func (w *LineWriter) bufferRune(r rune, state state, lineBreak bool) {
+	// Never add leading spaces to the buffer in the wordWrap state.
+	wordWrapNoLeadingSpaces := state == stateWordWrap && !lineBreak
+	switch kind := runeKind(r); kind {
+	case kindEOL:
+		// When we're word-wrapping and we see a letter followed by EOL, we convert
+		// the EOL into a single space in the buffer, to break the previous word
+		// from the next word.
+		if wordWrapNoLeadingSpaces && runeKind(w.prevRune) == kindLetter {
+			w.lineBuf.WriteRune(' ')
+		}
+	case kindSpace:
+		if wordWrapNoLeadingSpaces || state == stateVerbatim {
+			w.lineBuf.WriteRune(r)
+		}
+	case kindLetter:
+		w.lineBuf.WriteRune(r)
+	default:
+		panic(fmt.Errorf("textutil: bufferRune unhandled kind %d", kind))
+	}
+}
diff --git a/textutil/line_writer_test.go b/textutil/line_writer_test.go
new file mode 100644
index 0000000..29db23f
--- /dev/null
+++ b/textutil/line_writer_test.go
@@ -0,0 +1,268 @@
+package textutil
+
+import (
+	"bytes"
+	"io"
+	"strings"
+	"testing"
+)
+
+type lp struct {
+	line, para string
+}
+
+var (
+	allIndents  = [][]int{nil, {}, {1}, {2}, {1, 2}, {2, 1}}
+	allIndents1 = [][]int{{1}, {2}, {1, 2}, {2, 1}}
+)
+
+func TestLineWriter(t *testing.T) {
+	tests := []struct {
+		Width   int
+		Indents [][]int
+		In      string // See xlateIn for details on the format
+		Want    string // See xlateWant for details on the format
+	}{
+		// Completely blank input yields empty output.
+		{4, allIndents, "", ""},
+		{4, allIndents, " ", ""},
+		{4, allIndents, "  ", ""},
+		{4, allIndents, "   ", ""},
+		{4, allIndents, "    ", ""},
+		{4, allIndents, "     ", ""},
+		{4, allIndents, "      ", ""},
+		{4, allIndents, "F N  R   V    L     P      ", ""},
+		// Single words never get word-wrapped, even if they're long.
+		{4, allIndents, "a", "0a."},
+		{4, allIndents, "ab", "0ab."},
+		{4, allIndents, "abc", "0abc."},
+		{4, allIndents, "abcd", "0abcd."},
+		{4, allIndents, "abcde", "0abcde."},
+		{4, allIndents, "abcdef", "0abcdef."},
+		// Word-wrapping boundary conditions.
+		{4, allIndents, "abc ", "0abc."},
+		{4, allIndents, "abc  ", "0abc."},
+		{4, allIndents, "abcN", "0abc."},
+		{4, allIndents, "abcN ", "0abc."},
+		{4, allIndents, "abcd ", "0abcd."},
+		{4, allIndents, "abcd  ", "0abcd."},
+		{4, allIndents, "abcdN", "0abcd."},
+		{4, allIndents, "abcdN ", "0abcd."},
+		{4, [][]int{nil}, "a cd", "0a cd."},
+		{4, [][]int{nil}, "a cd ", "0a cd."},
+		{4, [][]int{nil}, "a cdN", "0a cd."},
+		{4, allIndents1, "a cd", "0a.1cd."},
+		{4, allIndents1, "a cd ", "0a.1cd."},
+		{4, allIndents1, "a cdN", "0a.1cd."},
+		{4, allIndents, "a cde", "0a.1cde."},
+		{4, allIndents, "a cde ", "0a.1cde."},
+		{4, allIndents, "a cdeN", "0a.1cde."},
+		{4, [][]int{nil}, "a  d", "0a  d."},
+		{4, [][]int{nil}, "a  d ", "0a  d."},
+		{4, [][]int{nil}, "a  dN", "0a  d."},
+		{4, allIndents1, "a  d", "0a.1d."},
+		{4, allIndents1, "a  d ", "0a.1d."},
+		{4, allIndents1, "a  dN", "0a.1d."},
+		{4, allIndents, "a  de", "0a.1de."},
+		{4, allIndents, "a  de ", "0a.1de."},
+		{4, allIndents, "a  deN", "0a.1de."},
+		// Multi-line word-wrapping boundary conditions.
+		{4, allIndents, "abc e", "0abc.1e."},
+		{4, allIndents, "abc.e", "0abc.1e."},
+		{4, allIndents, "abc efgh", "0abc.1efgh."},
+		{4, allIndents, "abc.efgh", "0abc.1efgh."},
+		{4, allIndents, "abc efghi", "0abc.1efghi."},
+		{4, allIndents, "abc.efghi", "0abc.1efghi."},
+		{4, [][]int{nil}, "abc e gh", "0abc.1e gh."},
+		{4, [][]int{nil}, "abc.e.gh", "0abc.1e gh."},
+		{4, allIndents1, "abc e gh", "0abc.1e.2gh."},
+		{4, allIndents1, "abc.e.gh", "0abc.1e.2gh."},
+		{4, allIndents, "abc e ghijk", "0abc.1e.2ghijk."},
+		{4, allIndents, "abc.e.ghijk", "0abc.1e.2ghijk."},
+		// Verbatim lines.
+		{4, allIndents, " b", "0 b."},
+		{4, allIndents, "  bc", "0  bc."},
+		{4, allIndents, "   bcd", "0   bcd."},
+		{4, allIndents, "    bcde", "0    bcde."},
+		{4, allIndents, "     bcdef", "0     bcdef."},
+		{4, allIndents, "      bcdefg", "0      bcdefg."},
+		{4, allIndents, " b de ghijk", "0 b de ghijk."},
+		// Verbatim lines before word-wrapped lines.
+		{4, allIndents, " b.vw yz", "0 b.1vw.2yz."},
+		{4, allIndents, "  bc.vw yz", "0  bc.1vw.2yz."},
+		{4, allIndents, "   bcd.vw yz", "0   bcd.1vw.2yz."},
+		{4, allIndents, "    bcde.vw yz", "0    bcde.1vw.2yz."},
+		{4, allIndents, "     bcdef.vw yz", "0     bcdef.1vw.2yz."},
+		{4, allIndents, "      bcdefg.vw yz", "0      bcdefg.1vw.2yz."},
+		{4, allIndents, " b de ghijk.vw yz", "0 b de ghijk.1vw.2yz."},
+		// Verbatim lines after word-wrapped lines.
+		{4, allIndents, "vw yz. b", "0vw.1yz.2 b."},
+		{4, allIndents, "vw yz.  bc", "0vw.1yz.2  bc."},
+		{4, allIndents, "vw yz.   bcd", "0vw.1yz.2   bcd."},
+		{4, allIndents, "vw yz.    bcde", "0vw.1yz.2    bcde."},
+		{4, allIndents, "vw yz.     bcdef", "0vw.1yz.2     bcdef."},
+		{4, allIndents, "vw yz.      bcdefg", "0vw.1yz.2      bcdefg."},
+		{4, allIndents, "vw yz. b de ghijk", "0vw.1yz.2 b de ghijk."},
+		// Verbatim lines between word-wrapped lines.
+		{4, allIndents, "vw yz. b.mn pq", "0vw.1yz.2 b.2mn.2pq."},
+		{4, allIndents, "vw yz.  bc.mn pq", "0vw.1yz.2  bc.2mn.2pq."},
+		{4, allIndents, "vw yz.   bcd.mn pq", "0vw.1yz.2   bcd.2mn.2pq."},
+		{4, allIndents, "vw yz.    bcde.mn pq", "0vw.1yz.2    bcde.2mn.2pq."},
+		{4, allIndents, "vw yz.     bcdef.mn pq", "0vw.1yz.2     bcdef.2mn.2pq."},
+		{4, allIndents, "vw yz.      bcdefg.mn pq", "0vw.1yz.2      bcdefg.2mn.2pq."},
+		{4, allIndents, "vw yz. b de ghijk.mn pq", "0vw.1yz.2 b de ghijk.2mn.2pq."},
+		// Multi-paragraphs via explicit U+2029, and multi-newline.
+		{4, allIndents, "ab de ghPij lm op", "0ab.1de.2gh.:0ij.1lm.2op."},
+		{4, allIndents, "ab.de.ghPij.lm.op", "0ab.1de.2gh.:0ij.1lm.2op."},
+		{4, allIndents, "ab de gh Pij lm op", "0ab.1de.2gh.:0ij.1lm.2op."},
+		{4, allIndents, "ab.de.gh Pij.lm.op", "0ab.1de.2gh.:0ij.1lm.2op."},
+		{4, allIndents, "ab de ghNNij lm op", "0ab.1de.2gh.:0ij.1lm.2op."},
+		{4, allIndents, "ab.de.ghNNij.lm.op", "0ab.1de.2gh.:0ij.1lm.2op."},
+		{4, allIndents, "ab de ghNNNij lm op", "0ab.1de.2gh.:0ij.1lm.2op."},
+		{4, allIndents, "ab.de.ghNNNij.lm.op", "0ab.1de.2gh.:0ij.1lm.2op."},
+		{4, allIndents, "ab de gh N Nij lm op", "0ab.1de.2gh.:0ij.1lm.2op."},
+		{4, allIndents, "ab.de.gh N Nij.lm.op", "0ab.1de.2gh.:0ij.1lm.2op."},
+		{4, allIndents, "ab de gh N N Nij lm op", "0ab.1de.2gh.:0ij.1lm.2op."},
+		{4, allIndents, "ab.de.gh N N Nij.lm.op", "0ab.1de.2gh.:0ij.1lm.2op."},
+		// Special-case /r/n is a single EOL, but may be combined.
+		{4, allIndents, "ab de ghRNij lm op", "0ab.1de.2gh.2ij.2lm.2op."},
+		{4, allIndents, "ab.de.ghRNij.lm.op", "0ab.1de.2gh.2ij.2lm.2op."},
+		{4, allIndents, "ab de gh RNij lm op", "0ab.1de.2gh.2ij.2lm.2op."},
+		{4, allIndents, "ab.de.gh RNij.lm.op", "0ab.1de.2gh.2ij.2lm.2op."},
+		{4, allIndents, "ab de ghRNRNij lm op", "0ab.1de.2gh.:0ij.1lm.2op."},
+		{4, allIndents, "ab.de.ghRNRNij.lm.op", "0ab.1de.2gh.:0ij.1lm.2op."},
+		{4, allIndents, "ab de gh RN RNij lm op", "0ab.1de.2gh.:0ij.1lm.2op."},
+		{4, allIndents, "ab.de.gh RN RNij.lm.op", "0ab.1de.2gh.:0ij.1lm.2op."},
+		{4, allIndents, "ab de ghR Nij lm op", "0ab.1de.2gh.:0ij.1lm.2op."},
+		{4, allIndents, "ab.de.ghR Nij.lm.op", "0ab.1de.2gh.:0ij.1lm.2op."},
+		// Line separator via explicit U+2028 ends lines, but not paragraphs.
+		{4, allIndents, "aLcd", "0a.1cd."},
+		{4, allIndents, "a Lcd", "0a.1cd."},
+		{4, allIndents, "aLLcd", "0a.1cd."},
+		{4, allIndents, "a LLcd", "0a.1cd."},
+		// 0 width ends up with one word per line, except verbatim lines.
+		{0, allIndents, "a c e", "0a.1c.2e."},
+		{0, allIndents, "a cd fghij", "0a.1cd.2fghij."},
+		{0, allIndents, "a. cd fghij.l n", "0a.1 cd fghij.2l.2n."},
+		// -1 width ends up with all words on same line, except verbatim lines.
+		{-1, allIndents, "a c e", "0a c e."},
+		{-1, allIndents, "a cd fghij", "0a cd fghij."},
+		{-1, allIndents, "a. cd fghij.l n", "0a.1 cd fghij.2l n."},
+	}
+	for _, test := range tests {
+		// Run with a variety of chunk sizes.
+		for _, sizes := range [][]int{nil, {1}, {2}, {1, 2}, {2, 1}} {
+			// Run with a variety of line terminators and paragraph separators.
+			for _, lp := range []lp{{}, {"\n", "\n"}, {"L", "P"}, {"LLL", "PPP"}} {
+				// Run with a variety of indents.
+				if len(test.Indents) == 0 {
+					t.Errorf("%d %q %q has no indents, use [][]int{nil} rather than nil", test.Width, test.In, test.Want)
+				}
+				for _, indents := range test.Indents {
+					var buf bytes.Buffer
+					w := newUTF8LineWriter(t, &buf, test.Width, lp, indents)
+					lineWriterWriteFlush(t, w, xlateIn(test.In), sizes)
+					if got, want := buf.String(), xlateWant(test.Want, lp, indents); got != want {
+						t.Errorf("%q sizes:%v lp:%q indents:%v got %q, want %q", test.In, sizes, lp, indents, got, want)
+					}
+				}
+			}
+		}
+	}
+}
+
+// xlateIn translates our test.In pattern into an actual input string to feed
+// into the writer.  The point is to make it easy to specify the various control
+// sequences in a single character, so it's easier to understand.
+func xlateIn(text string) string {
+	text = strings.Replace(text, "F", "\f", -1)
+	text = strings.Replace(text, "N", "\n", -1)
+	text = strings.Replace(text, ".", "\n", -1) // Also allow . for easier reading
+	text = strings.Replace(text, "R", "\r", -1)
+	text = strings.Replace(text, "V", "\v", -1)
+	text = strings.Replace(text, "L", "\u2028", -1)
+	text = strings.Replace(text, "P", "\u2029", -1)
+	return text
+}
+
+// xlateWant translates our test.Want pattern into an actual expected string to
+// compare against the output.  The point is to make it easy to read and write
+// the expected patterns, and to make it easy to test various indents.
+func xlateWant(text string, lp lp, indents []int) string {
+	// Dot "." and colon ":" in the want string indicate line terminators and
+	// paragraph separators, respectively.
+	line := lp.line
+	if line == "" {
+		line = "\n"
+	}
+	text = strings.Replace(text, ".", line, -1)
+	para := lp.para
+	if para == "" {
+		para = "\n"
+	}
+	text = strings.Replace(text, ":", para, -1)
+	// The numbers in the want string indicate paragraph line numbers, to make it
+	// easier to automatically replace for various indent configurations.
+	switch len(indents) {
+	case 0:
+		text = strings.Replace(text, "0", "", -1)
+		text = strings.Replace(text, "1", "", -1)
+		text = strings.Replace(text, "2", "", -1)
+	case 1:
+		text = strings.Replace(text, "0", spaces(indents[0]), -1)
+		text = strings.Replace(text, "1", spaces(indents[0]), -1)
+		text = strings.Replace(text, "2", spaces(indents[0]), -1)
+	case 2:
+		text = strings.Replace(text, "0", spaces(indents[0]), -1)
+		text = strings.Replace(text, "1", spaces(indents[1]), -1)
+		text = strings.Replace(text, "2", spaces(indents[1]), -1)
+	case 3:
+		text = strings.Replace(text, "0", spaces(indents[0]), -1)
+		text = strings.Replace(text, "1", spaces(indents[1]), -1)
+		text = strings.Replace(text, "2", spaces(indents[2]), -1)
+	}
+	return text
+}
+
+func spaces(count int) string {
+	return strings.Repeat(" ", count)
+}
+
+func newUTF8LineWriter(t *testing.T, buf io.Writer, width int, lp lp, indents []int) *LineWriter {
+	w := NewUTF8LineWriter(buf, width)
+	if lp.line != "" || lp.para != "" {
+		if err := w.SetLineTerminator(lp.line); err != nil {
+			t.Errorf("SetLineTerminator(%q) got %v, want nil", lp.line, err)
+		}
+		if err := w.SetParagraphSeparator(lp.para); err != nil {
+			t.Errorf("SetParagraphSeparator(%q) got %v, want nil", lp.para, err)
+		}
+	}
+	if indents != nil {
+		indentStrs := make([]string, len(indents))
+		for ix, indent := range indents {
+			indentStrs[ix] = spaces(indent)
+		}
+		if err := w.SetIndents(indentStrs...); err != nil {
+			t.Errorf("SetIndents(%v) got %v, want nil", indentStrs, err)
+		}
+	}
+	return w
+}
+
+func lineWriterWriteFlush(t *testing.T, w *LineWriter, text string, sizes []int) {
+	// Write chunks of different sizes until we've exhausted the input.
+	remain := text
+	for ix := 0; len(remain) > 0; ix++ {
+		var chunk []byte
+		chunk, remain = nextChunk(remain, sizes, ix)
+		got, err := w.Write(chunk)
+		if want := len(chunk); got != want || err != nil {
+			t.Errorf("%q Write(%q) got (%d,%v), want (%d,nil)", text, chunk, got, err, want)
+		}
+	}
+	// Flush the writer.
+	if err := w.Flush(); err != nil {
+		t.Errorf("%q Flush() got %v, want nil", text, err)
+	}
+}
diff --git a/textutil/rune.go b/textutil/rune.go
new file mode 100644
index 0000000..57db02e
--- /dev/null
+++ b/textutil/rune.go
@@ -0,0 +1,119 @@
+package textutil
+
+import (
+	"bytes"
+)
+
+// TODO(toddw): Add UTF16 support.
+
+const (
+	EOF                = rune(-1) // Indicates the end of a rune stream.
+	LineSeparator      = '\u2028' // Unicode line separator rune.
+	ParagraphSeparator = '\u2029' // Unicode paragraph separator rune.
+)
+
+// RuneEncoder is the interface to an encoder of a stream of runes into
+// bytes.Buffer.
+type RuneEncoder interface {
+	// Encode encodes r into buf.
+	Encode(r rune, buf *bytes.Buffer)
+}
+
+// RuneStreamDecoder is the interface to a decoder of a contiguous stream of
+// runes.
+type RuneStreamDecoder interface {
+	// Next returns the next rune.  Invalid encodings are returned as U+FFFD.
+	// Returns EOF at the end of the stream.
+	Next() rune
+	// BytePos returns the current byte position in the original data buffer.
+	BytePos() int
+}
+
+// RuneChunkDecoder is the interface to a decoder of a stream of encoded runes
+// that may be arbitrarily chunked.
+//
+// Implementations of RuneChunkDecoder are commonly used to implement io.Writer
+// wrappers, to handle buffering when chunk boundaries may occur in the middle
+// of an encoded rune.
+type RuneChunkDecoder interface {
+	// Decode returns a RuneStreamDecoder that decodes the data chunk.  Call Next
+	// repeatedly on the returned stream until it returns EOF to decode the chunk.
+	Decode(chunk []byte) RuneStreamDecoder
+	// DecodeLeftover returns a RuneStreamDecoder that decodes leftover buffered
+	// data.  Call Next repeatedly on the returned stream until it returns EOF to
+	// ensure all buffered data is processed.
+	DecodeLeftover() RuneStreamDecoder
+}
+
+// RuneChunkWrite is a helper that calls d.Decode(data) and repeatedly calls
+// Next in a loop, calling fn for every rune that is decoded.  Returns the
+// number of bytes in data that were successfully processed.  If fn returns an
+// error, Write will return with that error, without processing any more data.
+//
+// This is a convenience for implementing io.Writer, given a RuneChunkDecoder.
+func RuneChunkWrite(d RuneChunkDecoder, fn func(rune) error, data []byte) (int, error) {
+	stream := d.Decode(data)
+	for r := stream.Next(); r != EOF; r = stream.Next() {
+		if err := fn(r); err != nil {
+			return stream.BytePos(), err
+		}
+	}
+	return stream.BytePos(), nil
+}
+
+// RuneChunkFlush is a helper that calls d.DecodeLeftover and repeatedly calls
+// Next in a loop, calling fn for every rune that is decoded.  If fn returns an
+// error, Flush will return with that error, without processing any more data.
+//
+// This is a convenience for implementing an additional Flush() call on an
+// implementation of io.Writer, given a RuneChunkDecoder.
+func RuneChunkFlush(d RuneChunkDecoder, fn func(rune) error) error {
+	stream := d.DecodeLeftover()
+	for r := stream.Next(); r != EOF; r = stream.Next() {
+		if err := fn(r); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// bytePos and runePos distinguish positions that are used in either domain;
+// we're trying to avoid silly mistakes like adding a bytePos to a runePos.
+type bytePos int
+type runePos int
+
+// byteRuneBuffer maintains a buffer with both byte and rune based positions.
+type byteRuneBuffer struct {
+	enc     RuneEncoder
+	buf     bytes.Buffer
+	runeLen runePos
+}
+
+func (b *byteRuneBuffer) ByteLen() bytePos { return bytePos(b.buf.Len()) }
+func (b *byteRuneBuffer) RuneLen() runePos { return b.runeLen }
+func (b *byteRuneBuffer) Bytes() []byte    { return b.buf.Bytes() }
+
+func (b *byteRuneBuffer) Reset() {
+	b.buf.Reset()
+	b.runeLen = 0
+}
+
+// WriteRune writes r into b.
+func (b *byteRuneBuffer) WriteRune(r rune) {
+	b.enc.Encode(r, &b.buf)
+	b.runeLen++
+}
+
+// WriteString writes str into b.
+func (b *byteRuneBuffer) WriteString(str string) {
+	for _, r := range str {
+		b.WriteRune(r)
+	}
+}
+
+// WriteString0Runes writes str into b, not incrementing the rune length.
+func (b *byteRuneBuffer) WriteString0Runes(str string) {
+	for _, r := range str {
+		b.enc.Encode(r, &b.buf)
+	}
+}
diff --git a/textutil/utf8.go b/textutil/utf8.go
new file mode 100644
index 0000000..349033e
--- /dev/null
+++ b/textutil/utf8.go
@@ -0,0 +1,167 @@
+package textutil
+
+import (
+	"bytes"
+	"fmt"
+	"unicode/utf8"
+)
+
+// UTF8Encoder implements RuneEncoder for the UTF-8 encoding.
+type UTF8Encoder struct{}
+
+var _ RuneEncoder = UTF8Encoder{}
+
+// Encode encodes r into buf in the UTF-8 encoding.
+func (UTF8Encoder) Encode(r rune, buf *bytes.Buffer) { buf.WriteRune(r) }
+
+// UTF8ChunkDecoder implements RuneChunkDecoder for a stream of UTF-8 data that
+// is arbitrarily chunked.
+//
+// UTF-8 is a byte-wise encoding that may use multiple bytes to encode a single
+// rune.  This decoder buffers partial runes that have been split across chunks,
+// so that a full rune is returned when the subsequent data chunk is provided.
+//
+// This is commonly used to implement an io.Writer wrapper over UTF-8 text.  It
+// is useful since the data provided to Write calls may be arbitrarily chunked.
+//
+// The zero UTF8ChunkDecoder is a decoder with an empty buffer.
+type UTF8ChunkDecoder struct {
+	// The only state we keep is the last partial rune we've encountered.
+	partial    [utf8.UTFMax]byte
+	partialLen int
+}
+
+var _ RuneChunkDecoder = (*UTF8ChunkDecoder)(nil)
+
+// Decode returns a RuneStreamDecoder that decodes the data chunk.  Call Next
+// repeatedly on the returned stream until it returns EOF to decode the chunk.
+//
+// If the data is chunked in the middle of an encoded rune, the final partial
+// rune in the chunk will be buffered, and the next call to Decode will continue
+// by combining the buffered data with the next chunk.
+//
+// Invalid encodings are transformed into U+FFFD, one byte at a time.  See
+// unicode/utf8.DecodeRune for details.
+func (d *UTF8ChunkDecoder) Decode(chunk []byte) RuneStreamDecoder {
+	return &utf8Stream{d, chunk, 0}
+}
+
+// DecodeLeftover returns a RuneStreamDecoder that decodes leftover buffered
+// data.  Call Next repeatedly on the returned stream until it returns EOF to
+// ensure all buffered data is processed.
+//
+// Since the only data that is buffered is the final partial rune, the returned
+// RuneStreamDecoder will only contain U+FFFD or EOF.
+func (d *UTF8ChunkDecoder) DecodeLeftover() RuneStreamDecoder {
+	return &utf8LeftoverStream{d, 0}
+}
+
+// nextRune decodes the next rune, logically combining any previously buffered
+// data with the data chunk.  It returns the decoded rune and the byte size of
+// the data that was used for the decoding.
+//
+// The returned size may be > 0 even if the returned rune == EOF, if a partial
+// rune was detected and buffered.  The returned size may be 0 even if the
+// returned rune != EOF, if previously buffered data was decoded.
+func (d *UTF8ChunkDecoder) nextRune(data []byte) (rune, int) {
+	if d.partialLen > 0 {
+		return d.nextRunePartial(data)
+	}
+	r, size := utf8.DecodeRune(data)
+	if r == utf8.RuneError && !utf8.FullRune(data) {
+		// Initialize the partial rune buffer with remaining data.
+		d.partialLen = copy(d.partial[:], data)
+		return d.verifyPartial(d.partialLen, data)
+	}
+	return r, size
+}
+
+// nextRunePartial implements nextRune when there is a previously buffered
+// partial rune.
+func (d *UTF8ChunkDecoder) nextRunePartial(data []byte) (rune, int) {
+	// Append as much data as we can to the partial rune, and see if it's full.
+	oldLen := d.partialLen
+	d.partialLen += copy(d.partial[oldLen:], data)
+	if !utf8.FullRune(d.partial[:d.partialLen]) {
+		// We still don't have a full rune - keep waiting.
+		return d.verifyPartial(d.partialLen-oldLen, data)
+	}
+	// We finally have a full rune.
+	r, size := utf8.DecodeRune(d.partial[:d.partialLen])
+	if size < oldLen {
+		// This occurs when we have a multi-byte rune that has the right number of
+		// bytes, but is an invalid code point.
+		//
+		// Say oldLen=2, and we just received the third byte of a 3-byte rune which
+		// isn't a UTF-8 trailing byte.  In this case utf8.DecodeRune returns U+FFFD
+		// and size=1, to indicate we should skip the first byte.
+		//
+		// We shift the unread portion of the old partial data forward, and update
+		// the partial len so that it's strictly decreasing.  The strictly
+		// decreasing property isn't necessary for correctness, but helps avoid
+		// repeatedly copying data into the partial buffer unecessarily.
+		copy(d.partial[:], d.partial[size:oldLen])
+		d.partialLen = oldLen - size
+		return r, 0
+	}
+	// We've used all the old buffered data; start decoding directly from data.
+	d.partialLen = 0
+	return r, size - oldLen
+}
+
+// verifyPartial is called when we don't have a full rune, and ncopy bytes have
+// been copied from data into the decoder partial rune buffer.  We expect that
+// all data has been buffered and we return EOF and the total size of the data.
+func (d *UTF8ChunkDecoder) verifyPartial(ncopy int, data []byte) (rune, int) {
+	if ncopy < len(data) {
+		// Something's very wrong if we managed to fill d.partial without copying
+		// all the data; any sequence of utf8.UTFMax bytes must be a full rune.
+		panic(fmt.Errorf("UTF8ChunkDecoder: partial rune %v with leftover data %v", d.partial[:d.partialLen], data[ncopy:]))
+	}
+	return EOF, len(data)
+}
+
+// utf8Stream implements UTF8ChunkDecoder.Decode.
+type utf8Stream struct {
+	d    *UTF8ChunkDecoder
+	data []byte
+	pos  int
+}
+
+var _ RuneStreamDecoder = (*utf8Stream)(nil)
+
+func (s *utf8Stream) Next() rune {
+	if s.pos == len(s.data) {
+		return EOF
+	}
+	r, size := s.d.nextRune(s.data[s.pos:])
+	s.pos += size
+	return r
+}
+
+func (s *utf8Stream) BytePos() int {
+	return s.pos
+}
+
+// utf8LeftoverStream implements UTF8ChunkDecoder.DecodeLeftover.
+type utf8LeftoverStream struct {
+	d   *UTF8ChunkDecoder
+	pos int
+}
+
+var _ RuneStreamDecoder = (*utf8LeftoverStream)(nil)
+
+func (s *utf8LeftoverStream) Next() rune {
+	if s.d.partialLen == 0 {
+		return EOF
+	}
+	r, size := utf8.DecodeRune(s.d.partial[:s.d.partialLen])
+	copy(s.d.partial[:], s.d.partial[size:])
+	s.d.partialLen -= size
+	s.pos += size
+	return r
+}
+
+func (s *utf8LeftoverStream) BytePos() int {
+	return s.pos
+}
diff --git a/textutil/utf8_test.go b/textutil/utf8_test.go
new file mode 100644
index 0000000..7517bef
--- /dev/null
+++ b/textutil/utf8_test.go
@@ -0,0 +1,110 @@
+package textutil
+
+import (
+	"reflect"
+	"testing"
+)
+
+func TestUTF8ChunkDecoder(t *testing.T) {
+	r2 := "Δ"
+	r3 := "王"
+	r4 := "\U0001F680"
+	tests := []struct {
+		Text string
+		Want []rune
+	}{
+		{"", nil},
+		{"a", []rune{'a'}},
+		{"abc", []rune{'a', 'b', 'c'}},
+		{"abc def ghi", []rune{'a', 'b', 'c', ' ', 'd', 'e', 'f', ' ', 'g', 'h', 'i'}},
+		// 2-byte runes.
+		{"ΔΘΠΣΦ", []rune{'Δ', 'Θ', 'Π', 'Σ', 'Φ'}},
+		// 3-byte runes.
+		{"王普澤世界", []rune{'王', '普', '澤', '世', '界'}},
+		// 4-byte runes.
+		{"\U0001F680\U0001F681\U0001F682\U0001F683", []rune{'\U0001F680', '\U0001F681', '\U0001F682', '\U0001F683'}},
+		// Mixed-bytes.
+		{"aΔ王\U0001F680æ™®Θb", []rune{'a', 'Δ', '王', '\U0001F680', 'æ™®', 'Θ', 'b'}},
+		// Error runes translated to U+FFFD.
+		{"\uFFFD", []rune{'\uFFFD'}},
+		{"a\uFFFDb", []rune{'a', '\uFFFD', 'b'}},
+		{"\xFF", []rune{'\uFFFD'}},
+		{"a\xFFb", []rune{'a', '\uFFFD', 'b'}},
+		// Multi-byte full runes.
+		{r2, []rune{[]rune(r2)[0]}},
+		{r3, []rune{[]rune(r3)[0]}},
+		{r4, []rune{[]rune(r4)[0]}},
+		// Partial runes translated to U+FFFD.
+		{r2[:1], []rune{'\uFFFD'}},
+		{r3[:1], []rune{'\uFFFD'}},
+		{r3[:2], []rune{'\uFFFD', '\uFFFD'}},
+		{r4[:1], []rune{'\uFFFD'}},
+		{r4[:2], []rune{'\uFFFD', '\uFFFD'}},
+		{r4[:3], []rune{'\uFFFD', '\uFFFD', '\uFFFD'}},
+		// Leading partial runes translated to U+FFFD.
+		{r2[:1] + "b", []rune{'\uFFFD', 'b'}},
+		{r3[:1] + "b", []rune{'\uFFFD', 'b'}},
+		{r3[:2] + "b", []rune{'\uFFFD', '\uFFFD', 'b'}},
+		{r4[:1] + "b", []rune{'\uFFFD', 'b'}},
+		{r4[:2] + "b", []rune{'\uFFFD', '\uFFFD', 'b'}},
+		{r4[:3] + "b", []rune{'\uFFFD', '\uFFFD', '\uFFFD', 'b'}},
+		// Trailing partial runes translated to U+FFFD.
+		{"a" + r2[:1], []rune{'a', '\uFFFD'}},
+		{"a" + r3[:1], []rune{'a', '\uFFFD'}},
+		{"a" + r3[:2], []rune{'a', '\uFFFD', '\uFFFD'}},
+		{"a" + r4[:1], []rune{'a', '\uFFFD'}},
+		{"a" + r4[:2], []rune{'a', '\uFFFD', '\uFFFD'}},
+		{"a" + r4[:3], []rune{'a', '\uFFFD', '\uFFFD', '\uFFFD'}},
+		// Bracketed partial runes translated to U+FFFD.
+		{"a" + r2[:1] + "b", []rune{'a', '\uFFFD', 'b'}},
+		{"a" + r3[:1] + "b", []rune{'a', '\uFFFD', 'b'}},
+		{"a" + r3[:2] + "b", []rune{'a', '\uFFFD', '\uFFFD', 'b'}},
+		{"a" + r4[:1] + "b", []rune{'a', '\uFFFD', 'b'}},
+		{"a" + r4[:2] + "b", []rune{'a', '\uFFFD', '\uFFFD', 'b'}},
+		{"a" + r4[:3] + "b", []rune{'a', '\uFFFD', '\uFFFD', '\uFFFD', 'b'}},
+	}
+	for _, test := range tests {
+		// Run with a variety of chunk sizes.
+		for _, sizes := range [][]int{nil, {1}, {2}, {1, 2}, {2, 1}, {3}, {1, 2, 3}} {
+			got := runeChunkWriteFlush(t, test.Text, sizes)
+			if want := test.Want; !reflect.DeepEqual(got, want) {
+				t.Errorf("%q got %v, want %v", test.Text, got, want)
+			}
+		}
+	}
+}
+
+func runeChunkWriteFlush(t *testing.T, text string, sizes []int) []rune {
+	var dec UTF8ChunkDecoder
+	var runes []rune
+	addRune := func(r rune) error {
+		runes = append(runes, r)
+		return nil
+	}
+	// Write chunks of different sizes until we've exhausted the input text.
+	remain := text
+	for ix := 0; len(remain) > 0; ix++ {
+		var chunk []byte
+		chunk, remain = nextChunk(remain, sizes, ix)
+		got, err := RuneChunkWrite(&dec, addRune, chunk)
+		if want := len(chunk); got != want || err != nil {
+			t.Errorf("%q RuneChunkWrite(%q) got (%d,%v), want (%d,nil)", text, chunk, got, err, want)
+		}
+	}
+	// Flush the decoder.
+	if err := RuneChunkFlush(&dec, addRune); err != nil {
+		t.Errorf("%q RuneChunkFlush got %v, want nil", text, err)
+	}
+	return runes
+}
+
+func nextChunk(text string, sizes []int, index int) (chunk []byte, remain string) {
+	if len(sizes) == 0 {
+		return []byte(text), ""
+	}
+	size := sizes[index%len(sizes)]
+	if size >= len(text) {
+		return []byte(text), ""
+	}
+	return []byte(text[:size]), text[size:]
+}
diff --git a/textutil/util.go b/textutil/util.go
new file mode 100644
index 0000000..85402de
--- /dev/null
+++ b/textutil/util.go
@@ -0,0 +1,40 @@
+package textutil
+
+import (
+	"syscall"
+	"unsafe"
+)
+
+// TerminalSize returns the dimensions of the terminal, if it's available from
+// the OS, otherwise returns an error.
+func TerminalSize() (row, col int, _ error) {
+	// Try getting the terminal size from stdout, stderr and stdin respectively.
+	// We try each of these in turn because the mechanism we're using fails if any
+	// of the fds is redirected on the command line.  E.g. "tool | less" redirects
+	// the stdout of tool to the stdin of less, and will mean tool cannot retrieve
+	// the terminal size from stdout.
+	//
+	// TODO(toddw): This probably only works on some linux / unix variants; add
+	// build tags and support different platforms.
+	if row, col, err := terminalSize(syscall.Stdout); err == nil {
+		return row, col, err
+	}
+	if row, col, err := terminalSize(syscall.Stderr); err == nil {
+		return row, col, err
+	}
+	return terminalSize(syscall.Stdin)
+}
+
+func terminalSize(fd int) (int, int, error) {
+	var ws winsize
+	if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&ws))); err != 0 {
+		return 0, 0, err
+	}
+	return int(ws.row), int(ws.col), nil
+}
+
+// winsize must correspond to the struct defined in "sys/ioctl.h".  Do not
+// export this struct; it's a platform-specific implementation detail.
+type winsize struct {
+	row, col, xpixel, ypixel uint16
+}
diff --git a/textutil/writer.go b/textutil/writer.go
new file mode 100644
index 0000000..59a5efe
--- /dev/null
+++ b/textutil/writer.go
@@ -0,0 +1,54 @@
+package textutil
+
+import (
+	"bytes"
+	"io"
+)
+
+// PrefixWriter returns an io.Writer that wraps w, where the prefix is written
+// out immediately before the first non-empty Write call.
+func PrefixWriter(w io.Writer, prefix string) io.Writer {
+	return &prefixWriter{w, []byte(prefix)}
+}
+
+type prefixWriter struct {
+	w      io.Writer
+	prefix []byte
+}
+
+func (w *prefixWriter) Write(data []byte) (int, error) {
+	if w.prefix != nil && len(data) > 0 {
+		w.w.Write(w.prefix)
+		w.prefix = nil
+	}
+	return w.w.Write(data)
+}
+
+// ByteReplaceWriter returns an io.Writer that wraps w, where all occurrences of
+// the old byte are replaced with the new string on Write calls.
+func ByteReplaceWriter(w io.Writer, old byte, new string) io.Writer {
+	return &byteReplaceWriter{w, []byte{old}, []byte(new)}
+}
+
+type byteReplaceWriter struct {
+	w        io.Writer
+	old, new []byte
+}
+
+func (w *byteReplaceWriter) Write(data []byte) (int, error) {
+	replaced := bytes.Replace(data, w.old, w.new, -1)
+	if len(replaced) == 0 {
+		return len(data), nil
+	}
+	// Write the replaced data, and return the number of bytes in data that were
+	// written out, based on the proportion of replaced data written.  The
+	// important boundary cases are:
+	//   If all replaced data was written, we return n=len(data).
+	//   If not all replaced data was written, we return n<len(data).
+	n, err := w.w.Write(replaced)
+	return n * len(data) / len(replaced), err
+}
+
+// TODO(toddw): Add ReplaceWriter, which performs arbitrary string replacements.
+// This will need to buffer data and have an extra Flush() method, since the old
+// string may match across successive Write calls.
diff --git a/textutil/writer_test.go b/textutil/writer_test.go
new file mode 100644
index 0000000..37c5f1d
--- /dev/null
+++ b/textutil/writer_test.go
@@ -0,0 +1,90 @@
+package textutil
+
+import (
+	"bytes"
+	"fmt"
+	"testing"
+)
+
+func TestPrefixWriter(t *testing.T) {
+	tests := []struct {
+		Prefix string
+		Writes []string
+		Want   string
+	}{
+		{"", nil, ""},
+		{"", []string{""}, ""},
+		{"", []string{"a"}, "a"},
+		{"", []string{"a", ""}, "a"},
+		{"", []string{"", "a"}, "a"},
+		{"", []string{"a", "b"}, "ab"},
+		{"PRE", nil, ""},
+		{"PRE", []string{""}, ""},
+		{"PRE", []string{"a"}, "PREa"},
+		{"PRE", []string{"a", ""}, "PREa"},
+		{"PRE", []string{"", "a"}, "PREa"},
+		{"PRE", []string{"a", "b"}, "PREab"},
+	}
+	for _, test := range tests {
+		var buf bytes.Buffer
+		w := PrefixWriter(&buf, test.Prefix)
+		for _, write := range test.Writes {
+			name := fmt.Sprintf("(%v, %v)", test.Want, write)
+			n, err := w.Write([]byte(write))
+			if got, want := n, len(write); got != want {
+				t.Errorf("%s got len %d, want %d", name, got, want)
+			}
+			if err != nil {
+				t.Errorf("%s got error: %v", name, err)
+			}
+		}
+		if got, want := buf.String(), test.Want; got != want {
+			t.Errorf("got %v, want %v", got, want)
+		}
+	}
+}
+
+func TestByteReplaceWriter(t *testing.T) {
+	tests := []struct {
+		Old    byte
+		New    string
+		Writes []string
+		Want   string
+	}{
+		{'a', "", nil, ""},
+		{'a', "", []string{""}, ""},
+		{'a', "", []string{"a"}, ""},
+		{'a', "", []string{"b"}, "b"},
+		{'a', "", []string{"aba"}, "b"},
+		{'a', "", []string{"aba", "bab"}, "bbb"},
+		{'a', "X", nil, ""},
+		{'a', "X", []string{""}, ""},
+		{'a', "X", []string{"a"}, "X"},
+		{'a', "X", []string{"b"}, "b"},
+		{'a', "X", []string{"aba"}, "XbX"},
+		{'a', "X", []string{"aba", "bab"}, "XbXbXb"},
+		{'a', "ZZZ", nil, ""},
+		{'a', "ZZZ", []string{""}, ""},
+		{'a', "ZZZ", []string{"a"}, "ZZZ"},
+		{'a', "ZZZ", []string{"b"}, "b"},
+		{'a', "ZZZ", []string{"aba"}, "ZZZbZZZ"},
+		{'a', "ZZZ", []string{"aba", "bab"}, "ZZZbZZZbZZZb"},
+	}
+	for _, test := range tests {
+		var buf bytes.Buffer
+		w := ByteReplaceWriter(&buf, test.Old, test.New)
+		for _, write := range test.Writes {
+			name := fmt.Sprintf("(%v, %v, %v, %v)", test.Old, test.New, test.Want, write)
+			n, err := w.Write([]byte(write))
+			if got, want := n, len(write); got != want {
+				t.Errorf("%s got len %d, want %d", name, got, want)
+			}
+			if err != nil {
+				t.Errorf("%s got error: %v", name, err)
+			}
+		}
+		if got, want := buf.String(), test.Want; got != want {
+			t.Errorf("got %v, want %v", got, want)
+		}
+	}
+}
diff --git a/toposort/sort.go b/toposort/sort.go
new file mode 100644
index 0000000..26fdfa9
--- /dev/null
+++ b/toposort/sort.go
@@ -0,0 +1,132 @@
+// Package toposort implements topological sort.  For details see:
+// http://en.wikipedia.org/wiki/Topological_sorting
+package toposort
+
+// Sorter implements a topological sorter.  Add nodes and edges to the sorter to
+// describe the graph, and call Sort to retrieve topologically-sorted nodes.
+// The zero Sorter describes an empty graph.
+type Sorter struct {
+	values map[interface{}]int // maps from user-provided value to index in nodes
+	nodes  []*node             // the graph to sort
+}
+
+// node represents a node in the graph.
+type node struct {
+	value    interface{}
+	children []*node
+}
+
+func (s *Sorter) getOrAddNode(value interface{}) *node {
+	if s.values == nil {
+		s.values = make(map[interface{}]int)
+	}
+	if index, ok := s.values[value]; ok {
+		return s.nodes[index]
+	}
+	s.values[value] = len(s.nodes)
+	newNode := &node{value: value}
+	s.nodes = append(s.nodes, newNode)
+	return newNode
+}
+
+// AddNode adds a node.  Arbitrary value types are supported, but the values
+// must be comparable; they'll be used as map keys.  Typically this is only used
+// to add nodes with no incoming or outgoing edges.
+func (s *Sorter) AddNode(value interface{}) {
+	s.getOrAddNode(value)
+}
+
+// AddEdge adds nodes from and to, and adds an edge from -> to.  You don't need
+// to call AddNode first; the nodes will be implicitly added if they don't
+// already exist.  The direction means that from depends on to; i.e. to will
+// appear before from in the sorted output.  Cycles are allowed.
+func (s *Sorter) AddEdge(from interface{}, to interface{}) {
+	fromN, toN := s.getOrAddNode(from), s.getOrAddNode(to)
+	fromN.children = append(fromN.children, toN)
+}
+
+// Sort returns the topologically sorted nodes, along with some of the cycles
+// (if any) that were encountered.  You're guaranteed that len(cycles)==0 iff
+// there are no cycles in the graph, otherwise an arbitrary (but non-empty) list
+// of cycles is returned.
+//
+// If there are cycles the sorting is best-effort; portions of the graph that
+// are acyclic will still be ordered correctly, and the cyclic portions have an
+// arbitrary ordering.
+//
+// Sort is deterministic; given the same sequence of inputs it always returns
+// the same output, even if the inputs are only partially ordered.
+func (s *Sorter) Sort() (sorted []interface{}, cycles [][]interface{}) {
+	// The strategy is the standard simple approach of performing DFS on the
+	// graph.  Details are outlined in the above wikipedia article.
+	done := make(map[*node]bool)
+	for _, n := range s.nodes {
+		cycles = appendCycles(cycles, n.visit(done, make(map[*node]bool), &sorted))
+	}
+	return
+}
+
+// visit performs DFS on the graph, and fills in sorted and cycles as it
+// traverses.  We use done to indicate a node has been fully explored, and
+// visiting to indicate a node is currently being explored.
+//
+// The cycle collection strategy is to wait until we've hit a repeated node in
+// visiting, and add that node to cycles and return.  Thereafter as the
+// recursive stack is unwound, nodes append themselves to the end of each cycle,
+// until we're back at the repeated node.  This guarantees that if the graph is
+// cyclic we'll return at least one of the cycles.
+func (n *node) visit(done, visiting map[*node]bool, sorted *[]interface{}) (cycles [][]interface{}) {
+	if done[n] {
+		return
+	}
+	if visiting[n] {
+		cycles = [][]interface{}{{n.value}}
+		return
+	}
+	visiting[n] = true
+	for _, child := range n.children {
+		cycles = appendCycles(cycles, child.visit(done, visiting, sorted))
+	}
+	done[n] = true
+	*sorted = append(*sorted, n.value)
+	// Update cycles.  If it's empty none of our children detected any cycles, and
+	// there's nothing to do.  Otherwise we append ourselves to the cycle, iff the
+	// cycle hasn't completed yet.  We know the cycle has completed if the first
+	// and last item in the cycle are the same, with an exception for the single
+	// item case; self-cycles are represented as the same node appearing twice.
+	for cx := range cycles {
+		len := len(cycles[cx])
+		if len == 1 || cycles[cx][0] != cycles[cx][len-1] {
+			cycles[cx] = append(cycles[cx], n.value)
+		}
+	}
+	return
+}
+
+// appendCycles returns the combined cycles in a and b.
+func appendCycles(a [][]interface{}, b [][]interface{}) [][]interface{} {
+	for _, bcycle := range b {
+		a = append(a, bcycle)
+	}
+	return a
+}
+
+// DumpCycles dumps the cycles returned from Sorter.Sort, using toString to
+// convert each node into a string.
+func DumpCycles(cycles [][]interface{}, toString func(n interface{}) string) string {
+	var str string
+	for cyclex, cycle := range cycles {
+		if cyclex > 0 {
+			str += " "
+		}
+		str += "["
+		for nodex, node := range cycle {
+			if nodex > 0 {
+				str += " <= "
+			}
+			str += toString(node)
+		}
+		str += "]"
+	}
+	return str
+}
diff --git a/toposort/sort_test.go b/toposort/sort_test.go
new file mode 100644
index 0000000..f33fabc
--- /dev/null
+++ b/toposort/sort_test.go
@@ -0,0 +1,215 @@
+package toposort
+
+import (
+	"reflect"
+	"testing"
+)
+
+func toStringSlice(input []interface{}) (output []string) {
+	output = make([]string, len(input))
+	for ix, ival := range input {
+		output[ix] = ival.(string)
+	}
+	return
+}
+
+func toStringCycles(input [][]interface{}) (output [][]string) {
+	output = make([][]string, len(input))
+	for ix, islice := range input {
+		output[ix] = toStringSlice(islice)
+	}
+	return
+}
+
+type orderChecker struct {
+	t        *testing.T
+	original []string
+	orderMap map[string]int
+}
+
+func makeOrderChecker(t *testing.T, slice []interface{}) orderChecker {
+	result := orderChecker{t, toStringSlice(slice), make(map[string]int)}
+	for ix, val := range result.original {
+		result.orderMap[val] = ix
+	}
+	return result
+}
+
+func (oc *orderChecker) findValue(val string) int {
+	if index, ok := oc.orderMap[val]; ok {
+		return index
+	}
+	oc.t.Errorf("Couldn't find val %v in slice %v", val, oc.original)
+	return -1
+}
+
+func (oc *orderChecker) expectOrder(before, after string) {
+	if oc.findValue(before) >= oc.findValue(after) {
+		oc.t.Errorf("Expected %v before %v, slice %v", before, after, oc.original)
+	}
+}
+
+// Since sort is deterministic we can expect a particular total order, in
+// addition to the partial order checks.
+func (oc *orderChecker) expectTotalOrder(expect ...string) {
+	if !reflect.DeepEqual(oc.original, expect) {
+		oc.t.Errorf("Expected order %v, actual %v", expect, oc.original)
+	}
+}
+
+func expectCycles(t *testing.T, actual [][]interface{}, expect [][]string) {
+	actualStr := toStringCycles(actual)
+	if !reflect.DeepEqual(actualStr, expect) {
+		t.Errorf("Expected cycles %v, actual %v", expect, actualStr)
+	}
+}
+
+func TestSortDag(t *testing.T) {
+	// This is the graph:
+	// ,-->B
+	// |
+	// A-->C---->D
+	// |    \
+	// |     `-->E--.
+	// `-------------`-->F
+	var sorter Sorter
+	sorter.AddEdge("A", "B")
+	sorter.AddEdge("A", "C")
+	sorter.AddEdge("A", "F")
+	sorter.AddEdge("C", "D")
+	sorter.AddEdge("C", "E")
+	sorter.AddEdge("E", "F")
+	sorted, cycles := sorter.Sort()
+	oc := makeOrderChecker(t, sorted)
+	oc.expectOrder("B", "A")
+	oc.expectOrder("C", "A")
+	oc.expectOrder("D", "A")
+	oc.expectOrder("E", "A")
+	oc.expectOrder("F", "A")
+	oc.expectOrder("D", "C")
+	oc.expectOrder("E", "C")
+	oc.expectOrder("F", "C")
+	oc.expectOrder("F", "E")
+	oc.expectTotalOrder("B", "D", "F", "E", "C", "A")
+	expectCycles(t, cycles, [][]string{})
+}
+
+func TestSortSelfCycle(t *testing.T) {
+	// This is the graph:
+	// ,---.
+	// |   |
+	// A<--'
+	var sorter Sorter
+	sorter.AddEdge("A", "A")
+	sorted, cycles := sorter.Sort()
+	oc := makeOrderChecker(t, sorted)
+	oc.expectTotalOrder("A")
+	expectCycles(t, cycles, [][]string{{"A", "A"}})
+}
+
+func TestSortCycle(t *testing.T) {
+	// This is the graph:
+	// ,-->B-->C
+	// |       |
+	// A<------'
+	var sorter Sorter
+	sorter.AddEdge("A", "B")
+	sorter.AddEdge("B", "C")
+	sorter.AddEdge("C", "A")
+	sorted, cycles := sorter.Sort()
+	oc := makeOrderChecker(t, sorted)
+	oc.expectTotalOrder("C", "B", "A")
+	expectCycles(t, cycles, [][]string{{"A", "C", "B", "A"}})
+}
+
+func TestSortContainsCycle1(t *testing.T) {
+	// This is the graph:
+	// ,-->B
+	// |   ,-----.
+	// |   v     |
+	// A-->C---->D
+	// |    \
+	// |     `-->E--.
+	// `-------------`-->F
+	var sorter Sorter
+	sorter.AddEdge("A", "B")
+	sorter.AddEdge("A", "C")
+	sorter.AddEdge("A", "F")
+	sorter.AddEdge("C", "D")
+	sorter.AddEdge("C", "E")
+	sorter.AddEdge("D", "C") // creates the cycle
+	sorter.AddEdge("E", "F")
+	sorted, cycles := sorter.Sort()
+	oc := makeOrderChecker(t, sorted)
+	oc.expectOrder("B", "A")
+	oc.expectOrder("C", "A")
+	oc.expectOrder("D", "A")
+	oc.expectOrder("E", "A")
+	oc.expectOrder("F", "A")
+	// The difference with the dag is C, D may be in either order.
+	oc.expectOrder("E", "C")
+	oc.expectOrder("F", "C")
+	oc.expectOrder("F", "E")
+	oc.expectTotalOrder("B", "D", "F", "E", "C", "A")
+	expectCycles(t, cycles, [][]string{{"C", "D", "C"}})
+}
+
+func TestSortContainsCycle2(t *testing.T) {
+	// This is the graph:
+	// ,-->B
+	// |   ,-------------.
+	// |   v             |
+	// A-->C---->D       |
+	// |    \            |
+	// |     `-->E--.    |
+	// `-------------`-->F
+	var sorter Sorter
+	sorter.AddEdge("A", "B")
+	sorter.AddEdge("A", "C")
+	sorter.AddEdge("A", "F")
+	sorter.AddEdge("C", "D")
+	sorter.AddEdge("C", "E")
+	sorter.AddEdge("E", "F")
+	sorter.AddEdge("F", "C") // creates the cycle
+	sorted, cycles := sorter.Sort()
+	oc := makeOrderChecker(t, sorted)
+	oc.expectOrder("B", "A")
+	oc.expectOrder("C", "A")
+	oc.expectOrder("D", "A")
+	oc.expectOrder("E", "A")
+	oc.expectOrder("F", "A")
+	oc.expectOrder("D", "C")
+	// The difference with the dag is C, E, F may be in any order.
+	oc.expectTotalOrder("B", "D", "F", "E", "C", "A")
+	expectCycles(t, cycles, [][]string{{"C", "F", "E", "C"}})
+}
+
+func TestSortMultiCycles(t *testing.T) {
+	// This is the graph:
+	//    ,-->B
+	//    |   ,------------.
+	//    |   v            |
+	// .--A-->C---->D      |
+	// |  ^    \           |
+	// |  |     `-->E--.   |
+	// |  |         |  |   |
+	// |  `---------'  |   |
+	// `---------------`-->F
+	var sorter Sorter
+	sorter.AddEdge("A", "B")
+	sorter.AddEdge("A", "C")
+	sorter.AddEdge("A", "F")
+	sorter.AddEdge("C", "D")
+	sorter.AddEdge("C", "E")
+	sorter.AddEdge("E", "A") // creates a cycle
+	sorter.AddEdge("E", "F")
+	sorter.AddEdge("F", "C") // creates a cycle
+	sorted, cycles := sorter.Sort()
+	oc := makeOrderChecker(t, sorted)
+	oc.expectOrder("B", "A")
+	oc.expectOrder("D", "A")
+	oc.expectOrder("F", "A")
+	oc.expectOrder("D", "C")
+	oc.expectTotalOrder("B", "D", "F", "E", "C", "A")
+	expectCycles(t, cycles, [][]string{{"A", "E", "C", "A"}, {"C", "F", "E", "C"}})
+}
diff --git a/vlog/GO.PACKAGE b/vlog/GO.PACKAGE
new file mode 100644
index 0000000..0d3f6ae
--- /dev/null
+++ b/vlog/GO.PACKAGE
@@ -0,0 +1,7 @@
+{
+	"dependencies": {
+		"outgoing": [
+			{"allow": "github.com/cosmosnicolaou/llog"}
+		]
+	}
+}
diff --git a/vlog/doc.go b/vlog/doc.go
new file mode 100644
index 0000000..cf0fb3d
--- /dev/null
+++ b/vlog/doc.go
@@ -0,0 +1,69 @@
+// Package vlog defines and implements the veyron2 logging interfaces and
+// command line parsing. vlog is modeled on google3 and glog;
+// the differences from glog are:
+//
+// - interfaces are used to allow for multiple implementations and instances.
+//   In particular, application and runtime logging can be separated.
+//   We also expect to stream log messages to external log collectors rather
+//   to local storage.
+// - the Warn family of methods are not provided; their main use within
+//   google3 is to avoid the flush that's implicit in the Error routines
+//   rather than any semantic difference between warnings and errors.
+// - Info logging and Event logging is separated with the former expected
+//   to be somewhat spammy and the latter to be used sparingly. An error
+//   message that occurs during execution of a test should be treated as
+//   a failure of that test regardless of whether the test code itself
+//   passes. TODO(cnicolaou,toddw): implement this.
+// - Event logging includes methods for unconditionally (i.e. regardless
+//   of any command line options) logging the current goroutine's stack
+//   or the stacks of all goroutines.
+// - The use of interfaces and encapsulated state means that a single
+//   function (V) can no longer be used for 'if guarded' and 'chained' logging.
+//   That is:
+//     if vlog.V(1) { ... } and vlog.V(1).Infof( ... )
+//   become
+//     if logger.V(1) { ... }  and logger.VI(1).Infof( ... )
+//
+// vlog also creates a global instance of the Logger (vlog.Log) and
+// provides command line flags (see flags.go). Parsing of these flags is
+// performed by calling one of ConfigureLibraryLoggerFromFlags or
+// ConfigureLoggerFromFlags .
+//
+// The supported flags are:
+//
+//	-logtostderr=false
+//		Logs are written to standard error instead of to files.
+//	-alsologtostderr=false
+//		Logs are written to standard error as well as to files.
+//	-stderrthreshold=ERROR
+//		Log events at or above this severity are logged to standard
+//		error as well as to files.
+//	-log_dir=""
+//		Log files will be written to this directory instead of the
+//		default temporary directory.
+//
+//	Other flags provide aids to debugging.
+//
+//	-log_backtrace_at=""
+//		When set to a file and line number holding a logging statement,
+//		such as
+//			-log_backtrace_at=gopherflakes.go:234
+//		a stack trace will be written to the Info log whenever execution
+//		hits that statement. (Unlike with -vmodule, the ".go" must be
+//		present.)
+//	-v=0
+//		Enable V-leveled logging at the specified level.
+//	-vmodule=""
+//		The syntax of the argument is a comma-separated list of pattern=N,
+//		where pattern is a literal file name (minus the ".go" suffix) or
+//		"glob" pattern and N is a V level. For instance,
+//			-vmodule=gopher*=3
+//		sets the V level to 3 in all Go files whose names begin "gopher".
+//      -max_stack_buf_size=<size in bytes>
+//		Set the max size (bytes) of the byte buffer to use for stack
+//		traces. The default max is 4M; use powers of 2 since the
+//		stack size will be grown exponentially until it exceeds the max.
+//		A min of 128K is enforced and any attempts to reduce this will
+//		be silently ignored.
+//
+package vlog
diff --git a/vlog/flags.go b/vlog/flags.go
new file mode 100644
index 0000000..28811f8
--- /dev/null
+++ b/vlog/flags.go
@@ -0,0 +1,109 @@
+package vlog
+
+import (
+	"flag"
+	"fmt"
+
+	"github.com/cosmosnicolaou/llog"
+)
+
+var (
+	toStderr        bool
+	alsoToStderr    bool
+	logDir          string
+	verbosity       Level
+	stderrThreshold StderrThreshold = StderrThreshold(llog.ErrorLog)
+	vmodule         ModuleSpec
+	traceLocation   TraceLocation
+	maxStackBufSize int
+)
+
+var flagDefs = []struct {
+	name         string
+	variable     interface{}
+	defaultValue interface{}
+	description  string
+}{
+	{"log_dir", &logDir, "", "if non-empty, write log files to this directory"},
+	{"logtostderr", &toStderr, false, "log to standard error instead of files"},
+	{"alsologtostderr", &alsoToStderr, true, "log to standard error as well as files"},
+	{"max_stack_buf_size", &maxStackBufSize, 4192 * 1024, "max size in bytes of the buffer to use for logging stack traces"},
+	{"v", &verbosity, nil, "log level for V logs"},
+	{"stderrthreshold", &stderrThreshold, nil, "logs at or above this threshold go to stderr"},
+	{"vmodule", &vmodule, nil, "comma-separated list of pattern=N settings for file-filtered logging"},
+	{"log_backtrace_at", &traceLocation, nil, "when logging hits line file:N, emit a stack trace"},
+}
+
+func init() {
+	istest := false
+	if flag.CommandLine.Lookup("test.v") != nil {
+		istest = true
+	}
+	for _, flagDef := range flagDefs {
+		if istest && flagDef.name == "v" {
+			continue
+		}
+		switch v := flagDef.variable.(type) {
+		case *string:
+			flag.StringVar(v, flagDef.name,
+				flagDef.defaultValue.(string), flagDef.description)
+		case *bool:
+			flag.BoolVar(v, flagDef.name,
+				flagDef.defaultValue.(bool), flagDef.description)
+		case *int:
+			flag.IntVar(v, flagDef.name,
+				flagDef.defaultValue.(int), flagDef.description)
+		case flag.Value:
+			if flagDef.defaultValue != nil {
+				panic(fmt.Sprintf("default value not supported for flag %s", flagDef.name))
+			}
+			flag.Var(v, flagDef.name, flagDef.description)
+		default:
+			panic("invalid flag type")
+		}
+	}
+}
+
+// ConfigureLibraryLoggerFromFlags will configure the internal global logger
+// using command line flags.  It assumes that flag.Parse() has already been
+// called to initialize the flag variables.
+func ConfigureLibraryLoggerFromFlags() error {
+	return ConfigureLoggerFromFlags(Log)
+}
+
+// ConfigureLoggerFromLogs will configure the supplied logger using
+// command line flags.
+func ConfigureLoggerFromFlags(l Logger) error {
+	return l.Configure(
+		LogToStderr(toStderr),
+		AlsoLogToStderr(alsoToStderr),
+		LogDir(logDir),
+		Level(verbosity),
+		StderrThreshold(stderrThreshold),
+		ModuleSpec(vmodule),
+		TraceLocation(traceLocation),
+		MaxStackBufSize(maxStackBufSize),
+	)
+}
+
+func (l *logger) String() string {
+	return l.log.String()
+}
+
+// ExplicitlySetFlags returns a map of the logging command line flags and their
+// values formatted as strings.  Only the flags that were explicitly set are
+// returned. This is intended for use when an application needs to know what
+// value the flags were set to, for example when creating subprocesses.
+func (l *logger) ExplicitlySetFlags() map[string]string {
+	logFlagNames := make(map[string]bool)
+	for _, flagDef := range flagDefs {
+		logFlagNames[flagDef.name] = true
+	}
+	args := make(map[string]string)
+	flag.Visit(func(f *flag.Flag) {
+		if logFlagNames[f.Name] {
+			args[f.Name] = f.Value.String()
+		}
+	})
+	return args
+}
diff --git a/vlog/flags_test.go b/vlog/flags_test.go
new file mode 100644
index 0000000..1904b9c
--- /dev/null
+++ b/vlog/flags_test.go
@@ -0,0 +1,57 @@
+package vlog_test
+
+import (
+	"flag"
+	"fmt"
+	"io"
+	"os"
+	"path/filepath"
+	"testing"
+
+	"v.io/x/ref/lib/modules"
+
+	"v.io/x/lib/vlog"
+)
+
+//go:generate v23 test generate
+
+func child(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+	tmp := filepath.Join(os.TempDir(), "foo")
+	flag.Set("log_dir", tmp)
+	flag.Set("vmodule", "foo=2")
+	flags := vlog.Log.ExplicitlySetFlags()
+	if v, ok := flags["log_dir"]; !ok || v != tmp {
+		return fmt.Errorf("log_dir was supposed to be %v", tmp)
+	}
+	if v, ok := flags["vmodule"]; !ok || v != "foo=2" {
+		return fmt.Errorf("vmodule was supposed to be foo=2")
+	}
+	if f := flag.Lookup("max_stack_buf_size"); f == nil {
+		return fmt.Errorf("max_stack_buf_size is not a flag")
+	}
+	maxStackBufSizeSet := false
+	flag.Visit(func(f *flag.Flag) {
+		if f.Name == "max_stack_buf_size" {
+			maxStackBufSizeSet = true
+		}
+	})
+	if v, ok := flags["max_stack_buf_size"]; ok && !maxStackBufSizeSet {
+		return fmt.Errorf("max_stack_buf_size unexpectedly set to %v", v)
+	}
+	return nil
+}
+
+func TestFlags(t *testing.T) {
+	sh, err := modules.NewShell(nil, nil)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer sh.Cleanup(nil, nil)
+	h, err := sh.Start("child", nil)
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	if err = h.Shutdown(nil, os.Stderr); err != nil {
+		t.Errorf("unexpected error: %v", err)
+	}
+}
diff --git a/vlog/funcs.go b/vlog/funcs.go
new file mode 100644
index 0000000..394be7d
--- /dev/null
+++ b/vlog/funcs.go
@@ -0,0 +1,99 @@
+package vlog
+
+import (
+	"github.com/cosmosnicolaou/llog"
+)
+
+// Info logs to the INFO log.
+// Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
+func Info(args ...interface{}) {
+	Log.log.Print(llog.InfoLog, args...)
+	Log.maybeFlush()
+}
+
+// Infof logs to the INFO log.
+// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
+func Infof(format string, args ...interface{}) {
+	Log.log.Printf(llog.InfoLog, format, args...)
+	Log.maybeFlush()
+}
+
+// InfoStack logs the current goroutine's stack if the all parameter
+// is false, or the stacks of all goroutines if it's true.
+func InfoStack(all bool) {
+	infoStack(Log, all)
+}
+
+// V returns true if the configured logging level is greater than or equal to its parameter
+func V(level Level) bool {
+	return Log.log.V(llog.Level(level))
+}
+
+// VI is like V, except that it returns an instance of the Info
+// interface that will either log (if level >= the configured level)
+// or discard its parameters. This allows for logger.VI(2).Info
+// style usage.
+func VI(level Level) InfoLog {
+	if Log.log.V(llog.Level(level)) {
+		return Log
+	}
+	return &discardInfo{}
+}
+
+// Flush flushes all pending log I/O.
+func FlushLog() {
+	Log.FlushLog()
+}
+
+// Error logs to the ERROR and INFO logs.
+// Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
+func Error(args ...interface{}) {
+	Log.log.Print(llog.ErrorLog, args...)
+	Log.maybeFlush()
+}
+
+// Errorf logs to the ERROR and INFO logs.
+// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
+func Errorf(format string, args ...interface{}) {
+	Log.log.Printf(llog.ErrorLog, format, args...)
+	Log.maybeFlush()
+}
+
+// Fatal logs to the FATAL, ERROR and INFO logs,
+// including a stack trace of all running goroutines, then calls os.Exit(255).
+// Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
+func Fatal(args ...interface{}) {
+	Log.log.Print(llog.FatalLog, args...)
+}
+
+// Fatalf logs to the FATAL, ERROR and INFO logs,
+// including a stack trace of all running goroutines, then calls os.Exit(255).
+// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
+func Fatalf(format string, args ...interface{}) {
+	Log.log.Printf(llog.FatalLog, format, args...)
+}
+
+// ConfigureLogging configures all future logging. Some options
+// may not be usable if ConfigureLogging is called from an init function,
+// in which case an error will be returned. The Configured error is
+// returned if ConfigureLogger has already been called unless the
+// OverridePriorConfiguration options is included.
+func Configure(opts ...LoggingOpts) error {
+	return Log.Configure(opts...)
+}
+
+// Stats returns stats on how many lines/bytes haven been written to
+// this set of logs.
+func Stats() LevelStats {
+	return Log.Stats()
+}
+
+// Panic is equivalent to Error() followed by a call to panic().
+func Panic(args ...interface{}) {
+	Log.Panic(args...)
+}
+
+// Panicf is equivalent to Errorf() followed by a call to panic().
+func Panicf(format string, args ...interface{}) {
+	Log.Panicf(format, args...)
+}
diff --git a/vlog/log.go b/vlog/log.go
new file mode 100644
index 0000000..b020c03
--- /dev/null
+++ b/vlog/log.go
@@ -0,0 +1,208 @@
+package vlog
+
+import (
+	"errors"
+	"fmt"
+	"os"
+	"runtime"
+	"sync"
+
+	"github.com/cosmosnicolaou/llog"
+)
+
+const (
+	initialMaxStackBufSize = 128 * 1024
+)
+
+type logger struct {
+	log             *llog.Log
+	mu              sync.Mutex // guards updates to the vars below.
+	autoFlush       bool
+	maxStackBufSize int
+	logDir          string
+	configured      bool
+}
+
+func (l *logger) maybeFlush() {
+	if l.autoFlush {
+		l.log.Flush()
+	}
+}
+
+var (
+	Log        *logger
+	Configured = errors.New("logger has already been configured")
+)
+
+const stackSkip = 1
+
+func init() {
+	Log = &logger{log: llog.NewLogger("veyron", stackSkip)}
+}
+
+// NewLogger creates a new instance of the logging interface.
+func NewLogger(name string) Logger {
+	// Create an instance of the runtime with just logging enabled.
+	return &logger{log: llog.NewLogger(name, stackSkip)}
+}
+
+// Configure configures all future logging. Some options
+// may not be usable if ConfigureLogging is called from an init function,
+// in which case an error will be returned. The Configured error is returned
+// if ConfigureLogger has already been called unless the
+// OverridePriorConfiguration options is included.
+func (l *logger) Configure(opts ...LoggingOpts) error {
+	l.mu.Lock()
+	defer l.mu.Unlock()
+	override := false
+	for _, o := range opts {
+		switch v := o.(type) {
+		case OverridePriorConfiguration:
+			override = bool(v)
+		}
+	}
+	if l.configured && !override {
+		return Configured
+	}
+	for _, o := range opts {
+		switch v := o.(type) {
+		case AlsoLogToStderr:
+			l.log.SetAlsoLogToStderr(bool(v))
+		case Level:
+			l.log.SetV(llog.Level(v))
+		case LogDir:
+			l.logDir = string(v)
+			l.log.SetLogDir(l.logDir)
+		case LogToStderr:
+			l.log.SetLogToStderr(bool(v))
+		case MaxStackBufSize:
+			sz := int(v)
+			if sz > initialMaxStackBufSize {
+				l.maxStackBufSize = sz
+				l.log.SetMaxStackBufSize(sz)
+			}
+		case ModuleSpec:
+			l.log.SetVModule(v.ModuleSpec)
+		case TraceLocation:
+			l.log.SetTraceLocation(v.TraceLocation)
+		case StderrThreshold:
+			l.log.SetStderrThreshold(llog.Severity(v))
+		case AutoFlush:
+			l.autoFlush = bool(v)
+		}
+	}
+	l.configured = true
+	return nil
+}
+
+// LogDir returns the directory where the log files are written.
+func (l *logger) LogDir() string {
+	if len(l.logDir) != 0 {
+		return l.logDir
+	}
+	return os.TempDir()
+}
+
+// Stats returns stats on how many lines/bytes haven been written to
+// this set of logs.
+func (l *logger) Stats() LevelStats {
+	return LevelStats(l.log.Stats())
+}
+
+// Info logs to the INFO log.
+// Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
+func (l *logger) Info(args ...interface{}) {
+	l.log.Print(llog.InfoLog, args...)
+	l.maybeFlush()
+}
+
+// Infof logs to the INFO log.
+// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
+func (l *logger) Infof(format string, args ...interface{}) {
+	l.log.Printf(llog.InfoLog, format, args...)
+	l.maybeFlush()
+}
+
+func infoStack(l *logger, all bool) {
+	n := initialMaxStackBufSize
+	var trace []byte
+	for n <= l.maxStackBufSize {
+		trace = make([]byte, n)
+		nbytes := runtime.Stack(trace, all)
+		if nbytes < len(trace) {
+			l.log.Printf(llog.InfoLog, "%s", trace[:nbytes])
+			return
+		}
+		n *= 2
+	}
+	l.log.Printf(llog.InfoLog, "%s", trace)
+	l.maybeFlush()
+}
+
+// InfoStack logs the current goroutine's stack if the all parameter
+// is false, or the stacks of all goroutines if it's true.
+func (l *logger) InfoStack(all bool) {
+	infoStack(l, all)
+}
+
+func (l *logger) V(v Level) bool {
+	return l.log.V(llog.Level(v))
+}
+
+type discardInfo struct{}
+
+func (_ *discardInfo) Info(args ...interface{})                 {}
+func (_ *discardInfo) Infof(format string, args ...interface{}) {}
+func (_ *discardInfo) InfoStack(all bool)                       {}
+
+func (l *logger) VI(v Level) InfoLog {
+	if l.log.V(llog.Level(v)) {
+		return l
+	}
+	return &discardInfo{}
+}
+
+// Flush flushes all pending log I/O.
+func (l *logger) FlushLog() {
+	l.log.Flush()
+}
+
+// Error logs to the ERROR and INFO logs.
+// Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
+func (l *logger) Error(args ...interface{}) {
+	l.log.Print(llog.ErrorLog, args...)
+	l.maybeFlush()
+}
+
+// Errorf logs to the ERROR and INFO logs.
+// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
+func (l *logger) Errorf(format string, args ...interface{}) {
+	l.log.Printf(llog.ErrorLog, format, args...)
+	l.maybeFlush()
+}
+
+// Fatal logs to the FATAL, ERROR and INFO logs,
+// including a stack trace of all running goroutines, then calls os.Exit(255).
+// Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
+func (l *logger) Fatal(args ...interface{}) {
+	l.log.Print(llog.FatalLog, args...)
+}
+
+// Fatalf logs to the FATAL, ERROR and INFO logs,
+// including a stack trace of all running goroutines, then calls os.Exit(255).
+// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
+func (l *logger) Fatalf(format string, args ...interface{}) {
+	l.log.Printf(llog.FatalLog, format, args...)
+}
+
+// Panic is equivalent to Error() followed by a call to panic().
+func (l *logger) Panic(args ...interface{}) {
+	l.Error(args...)
+	panic(fmt.Sprint(args...))
+}
+
+// Panicf is equivalent to Errorf() followed by a call to panic().
+func (l *logger) Panicf(format string, args ...interface{}) {
+	l.Errorf(format, args...)
+	panic(fmt.Sprintf(format, args...))
+}
diff --git a/vlog/log_test.go b/vlog/log_test.go
new file mode 100644
index 0000000..3b7f7a8
--- /dev/null
+++ b/vlog/log_test.go
@@ -0,0 +1,193 @@
+package vlog_test
+
+import (
+	"bufio"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"regexp"
+	"testing"
+
+	"v.io/x/lib/vlog"
+)
+
+func ExampleConfigure() {
+	vlog.Configure()
+}
+
+func ExampleInfo() {
+	vlog.Info("hello")
+}
+
+func ExampleError() {
+	vlog.Errorf("%s", "error")
+	if vlog.V(2) {
+		vlog.Info("some spammy message")
+	}
+	vlog.VI(2).Infof("another spammy message")
+}
+
+func readLogFiles(dir string) ([]string, error) {
+	files, err := ioutil.ReadDir(dir)
+	if err != nil {
+		return nil, err
+	}
+	var contents []string
+	for _, fi := range files {
+		// Skip symlinks to avoid double-counting log lines.
+		if !fi.Mode().IsRegular() {
+			continue
+		}
+		file, err := os.Open(filepath.Join(dir, fi.Name()))
+		if err != nil {
+			return nil, err
+		}
+		scanner := bufio.NewScanner(file)
+		for scanner.Scan() {
+			if line := scanner.Text(); len(line) > 0 && line[0] == 'I' {
+				contents = append(contents, line)
+			}
+		}
+	}
+	return contents, nil
+}
+
+func TestHeaders(t *testing.T) {
+	dir, err := ioutil.TempDir("", "logtest")
+	defer os.RemoveAll(dir)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	logger := vlog.NewLogger("testHeader")
+	logger.Configure(vlog.LogDir(dir), vlog.Level(2))
+	logger.Infof("abc\n")
+	logger.Infof("wombats\n")
+	logger.VI(1).Infof("wombats again\n")
+	logger.FlushLog()
+	contents, err := readLogFiles(dir)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	fileRE := regexp.MustCompile(`\S+ \S+ \S+ (.*):.*`)
+	for _, line := range contents {
+		name := fileRE.FindStringSubmatch(line)
+		if len(name) < 2 {
+			t.Errorf("failed to find file in %s", line)
+			continue
+		}
+		if name[1] != "log_test.go" {
+			t.Errorf("unexpected file name: %s", name[1])
+			continue
+		}
+	}
+	if want, got := 3, len(contents); want != got {
+		t.Errorf("Expected %d info lines, got %d instead", want, got)
+	}
+}
+
+func myLoggedFunc() {
+	f := vlog.LogCall("entry")
+	f("exit")
+}
+
+func TestLogCall(t *testing.T) {
+	dir, err := ioutil.TempDir("", "logtest")
+	defer os.RemoveAll(dir)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	logger := vlog.NewLogger("testHeader")
+	logger.Configure(vlog.LogDir(dir), vlog.Level(2))
+	saveLog := vlog.Log
+	defer vlog.SetLog(saveLog)
+	vlog.SetLog(logger)
+
+	myLoggedFunc()
+	vlog.FlushLog()
+	contents, err := readLogFiles(dir)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	logCallLineRE := regexp.MustCompile(`\S+ \S+ \S+ ([^:]*):.*(call|return)\[(\S*)`)
+	for _, line := range contents {
+		match := logCallLineRE.FindStringSubmatch(line)
+		if len(match) != 4 {
+			t.Errorf("failed to match %s", line)
+			continue
+		}
+		fileName, callType, funcName := match[1], match[2], match[3]
+		if fileName != "log_test.go" {
+			t.Errorf("unexpected file name: %s", fileName)
+			continue
+		}
+		if callType != "call" && callType != "return" {
+			t.Errorf("unexpected call type: %s", callType)
+		}
+		if funcName != "vlog_test.myLoggedFunc" {
+			t.Errorf("unexpected func name: %s", funcName)
+		}
+	}
+	if want, got := 2, len(contents); want != got {
+		t.Errorf("Expected %d info lines, got %d instead", want, got)
+	}
+}
+
+func TestVModule(t *testing.T) {
+	dir, err := ioutil.TempDir("", "logtest")
+	defer os.RemoveAll(dir)
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	logger := vlog.NewLogger("testVmodule")
+	logger.Configure(vlog.LogDir(dir))
+	if logger.V(2) || logger.V(3) {
+		t.Errorf("Logging should not be enabled at levels 2 & 3")
+	}
+	spec := vlog.ModuleSpec{}
+	if err := spec.Set("*log_test=2"); err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	if err := logger.Configure(vlog.OverridePriorConfiguration(true), spec); err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	if !logger.V(2) {
+		t.Errorf("logger.V(2) should be true")
+	}
+	if logger.V(3) {
+		t.Errorf("logger.V(3) should be false")
+	}
+	if vlog.V(2) || vlog.V(3) {
+		t.Errorf("Logging should not be enabled at levels 2 & 3")
+	}
+	vlog.Log.Configure(vlog.OverridePriorConfiguration(true), spec)
+	if !vlog.V(2) {
+		t.Errorf("vlog.V(2) should be true")
+	}
+	if vlog.V(3) {
+		t.Errorf("vlog.V(3) should be false")
+	}
+	if vlog.VI(2) != vlog.Log {
+		t.Errorf("vlog.V(2) should be vlog.Log")
+	}
+	if vlog.VI(3) == vlog.Log {
+		t.Errorf("vlog.V(3) should not be vlog.Log")
+	}
+}
+
+func TestConfigure(t *testing.T) {
+	dir, err := ioutil.TempDir("", "logtest")
+	defer os.RemoveAll(dir)
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	logger := vlog.NewLogger("testVmodule")
+	if got, want := logger.Configure(vlog.LogDir(dir), vlog.AlsoLogToStderr(false)), error(nil); got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+	if got, want := logger.Configure(vlog.AlsoLogToStderr(true)), vlog.Configured; got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+	if got, want := logger.Configure(vlog.OverridePriorConfiguration(true), vlog.AlsoLogToStderr(false)), error(nil); got != want {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+}
diff --git a/vlog/logcall.go b/vlog/logcall.go
new file mode 100644
index 0000000..cfcffd1
--- /dev/null
+++ b/vlog/logcall.go
@@ -0,0 +1,142 @@
+package vlog
+
+import (
+	"fmt"
+	"path"
+	"reflect"
+	"runtime"
+	"sync/atomic"
+
+	"github.com/cosmosnicolaou/llog"
+)
+
+// logCallLogLevel is the log level beyond which calls are logged.
+const logCallLogLevel = 1
+
+func callerFuncName() string {
+	var funcName string
+	pc, _, _, ok := runtime.Caller(stackSkip + 1)
+	if ok {
+		function := runtime.FuncForPC(pc)
+		if function != nil {
+			funcName = path.Base(function.Name())
+		}
+	}
+	return funcName
+}
+
+// LogCall logs that its caller has been called given the arguments
+// passed to it.  It returns a function that is supposed to be called
+// when the caller returns, logging the caller’s return along with the
+// arguments it is provided with.
+// File name and line number of the call site and a randomly generated
+// invocation identifier is logged automatically.  The path through which
+// the caller function returns will be logged automatically too.
+//
+// The canonical way to use LogCall is along the lines of the following:
+//
+//     func Function(a Type1, b Type2) ReturnType {
+//         defer vlog.LogCall(a, b)()
+//         // ... function body ...
+//         return retVal
+//     }
+//
+// To log the return value as the function returns, the following
+// pattern should be used.  Note that pointers to the output
+// variables should be passed to the returning function, not the
+// variables themselves:
+//
+//     func Function(a Type1, b Type2) (r ReturnType) {
+//         defer vlog.LogCall(a, b)(&r)
+//         // ... function body ...
+//         return computeReturnValue()
+//     }
+//
+// Note that when using this pattern, you do not need to actually
+// assign anything to the named return variable explicitly.  A regular
+// return statement would automatically do the proper return variable
+// assignments.
+//
+// The log injector tool will automatically insert a LogCall invocation
+// into all implementations of the public API it runs, unless a Valid
+// Log Construct is found.  A Valid Log Construct is defined as one of
+// the following at the beginning of the function body (i.e. should not
+// be preceded by any non-whitespace or non-comment tokens):
+//     1. defer vlog.LogCall(optional arguments)(optional pointers to return values)
+//     2. defer vlog.LogCallf(argsFormat, optional arguments)(returnValuesFormat, optional pointers to return values)
+//     3. // nologcall
+//
+// The comment "// nologcall" serves as a hint to log injection and
+// checking tools to exclude the function from their consideration.
+// It is used as follows:
+//
+//     func FunctionWithoutLogging(args ...interface{}) {
+//         // nologcall
+//         // ... function body ...
+//     }
+//
+func LogCall(v ...interface{}) func(...interface{}) {
+	if !V(logCallLogLevel) {
+		return func(...interface{}) {}
+	}
+	callerFuncName := callerFuncName()
+	invocationId := newInvocationIdentifier()
+	if len(v) > 0 {
+		Log.log.Printf(llog.InfoLog, "call[%s %s]: args:%v", callerFuncName, invocationId, v)
+	} else {
+		Log.log.Printf(llog.InfoLog, "call[%s %s]", callerFuncName, invocationId)
+	}
+	return func(v ...interface{}) {
+		if len(v) > 0 {
+			Log.log.Printf(llog.InfoLog, "return[%s %s]: %v", callerFuncName, invocationId, derefSlice(v))
+		} else {
+			Log.log.Printf(llog.InfoLog, "return[%s %s]", callerFuncName, invocationId)
+		}
+	}
+}
+
+// LogCallf behaves identically to LogCall, except it lets the caller to
+// customize the log messages via format specifiers, like the following:
+//
+//     func Function(a Type1, b Type2) (r, t ReturnType) {
+//         defer vlog.LogCallf("a: %v, b: %v", a, b)("(r,t)=(%v,%v)", &r, &t)
+//         // ... function body ...
+//         return finalR, finalT
+//     }
+//
+func LogCallf(format string, v ...interface{}) func(string, ...interface{}) {
+	if !V(logCallLogLevel) {
+		return func(string, ...interface{}) {}
+	}
+	callerFuncName := callerFuncName()
+	invocationId := newInvocationIdentifier()
+	Log.log.Printf(llog.InfoLog, "call[%s %s]: %s", callerFuncName, invocationId, fmt.Sprintf(format, v...))
+	return func(format string, v ...interface{}) {
+		Log.log.Printf(llog.InfoLog, "return[%s %s]: %v", callerFuncName, invocationId, fmt.Sprintf(format, derefSlice(v)...))
+	}
+}
+
+func derefSlice(slice []interface{}) []interface{} {
+	o := make([]interface{}, 0, len(slice))
+	for _, x := range slice {
+		o = append(o, reflect.Indirect(reflect.ValueOf(x)).Interface())
+	}
+	return o
+}
+
+var invocationCounter uint64 = 0
+
+// newInvocationIdentifier generates a unique identifier for a method invocation
+// to make it easier to match up log lines for the entry and exit of a function
+// when looking at a log transcript.
+func newInvocationIdentifier() string {
+	const (
+		charSet    = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"
+		charSetLen = uint64(len(charSet))
+	)
+	r := []byte{'@'}
+	for n := atomic.AddUint64(&invocationCounter, 1); n > 0; n /= charSetLen {
+		r = append(r, charSet[n%charSetLen])
+	}
+	return string(r)
+}
diff --git a/vlog/model.go b/vlog/model.go
new file mode 100644
index 0000000..6a4ea96
--- /dev/null
+++ b/vlog/model.go
@@ -0,0 +1,153 @@
+package vlog
+
+import (
+	// TODO(cnicolaou): remove this dependency in the future. For now this
+	// saves us some code.
+	"github.com/cosmosnicolaou/llog"
+)
+
+type InfoLog interface {
+	// Info logs to the INFO log.
+	// Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
+	Info(args ...interface{})
+
+	// Infoln logs to the INFO log.
+	// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
+	Infof(format string, args ...interface{})
+
+	// InfoStack logs the current goroutine's stack if the all parameter
+	// is false, or the stacks of all goroutines if it's true.
+	InfoStack(all bool)
+}
+
+type Verbosity interface {
+	// V returns true if the configured logging level is greater than or equal to its parameter
+	V(level Level) bool
+	// VI is like V, except that it returns an instance of the Info
+	// interface that will either log (if level >= the configured level)
+	// or discard its parameters. This allows for logger.VI(2).Info
+	// style usage.
+	VI(level Level) InfoLog
+}
+
+// Level specifies a level of verbosity for V logs.
+// It can be set via the Level optional parameter to Configure.
+// It implements the flag.Value interface to support command line option parsing.
+type Level llog.Level
+
+// Set is part of the flag.Value interface.
+func (l *Level) Set(v string) error {
+	return (*llog.Level)(l).Set(v)
+}
+
+// Get is part of the flag.Value interface.
+func (l *Level) Get(v string) interface{} {
+	return *l
+}
+
+// String is part of the flag.Value interface.
+func (l *Level) String() string {
+	return (*llog.Level)(l).String()
+}
+
+// StderrThreshold identifies the sort of log: info, warning etc.
+// The values match the corresponding constants in C++ - e.g WARNING etc.
+// It can be set via the StderrThreshold optional parameter to Configure.
+// It implements the flag.Value interface to support command line option parsing.
+type StderrThreshold llog.Severity
+
+// Set is part of the flag.Value interface.
+func (s *StderrThreshold) Set(v string) error {
+	return (*llog.Severity)(s).Set(v)
+}
+
+// Get is part of the flag.Value interface.
+func (s *StderrThreshold) Get(v string) interface{} {
+	return *s
+}
+
+// String is part of the flag.Value interface.
+func (s *StderrThreshold) String() string {
+	return (*llog.Severity)(s).String()
+}
+
+// ModuleSpec allows for the setting of specific log levels for specific
+// modules. The syntax is recordio=2,file=1,gfs*=3
+// It can be set via the ModuleSpec optional parameter to Configure.
+// It implements the flag.Value interface to support command line option parsing.
+type ModuleSpec struct {
+	llog.ModuleSpec
+}
+
+// TraceLocation specifies the location, file:N, which when encountered will
+// cause logging to emit a stack trace.
+// It can be set via the TraceLocation optional parameter to Configure.
+// It implements the flag.Value interface to support command line option parsing.
+type TraceLocation struct {
+	llog.TraceLocation
+}
+
+// LevelStats tracks the number of lines of output and number of bytes
+// per severity level.
+type LevelStats llog.Stats
+
+type Logger interface {
+	InfoLog
+	Verbosity
+
+	// Flush flushes all pending log I/O.
+	FlushLog()
+
+	// Error logs to the ERROR and INFO logs.
+	// Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
+	Error(args ...interface{})
+
+	// Errorf logs to the ERROR and INFO logs.
+	// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
+	Errorf(format string, args ...interface{})
+
+	// Fatal logs to the FATAL, ERROR and INFO logs,
+	// including a stack trace of all running goroutines, then calls os.Exit(255).
+	// Arguments are handled in the manner of fmt.Print; a newline is appended if missing.
+	Fatal(args ...interface{})
+
+	// Fatalf logs to the FATAL, ERROR and INFO logs,
+	// including a stack trace of all running goroutines, then calls os.Exit(255).
+	// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.
+	Fatalf(format string, args ...interface{})
+
+	// Panic is equivalent to Error() followed by a call to panic().
+	Panic(args ...interface{})
+
+	// Panicf is equivalent to Errorf() followed by a call to panic().
+	Panicf(format string, args ...interface{})
+
+	// Configure configures all future logging. Some options
+	// may not be usable if Configure is called from an init function,
+	// in which case an error will be returned. The Configured error is
+	// returned if ConfigureLogger has already been called unless the
+	// OverridePriorConfiguration options is included.
+	// Some options only take effect if they are set before the logger
+	// is used.  Once anything is logged using the logger, these options
+	// will silently be ignored.  For example, LogDir, LogToStderr or
+	// AlsoLogToStderr fall in this category.
+	Configure(opts ...LoggingOpts) error
+
+	// Stats returns stats on how many lines/bytes haven been written to
+	// this set of logs per severity level.
+	Stats() LevelStats
+
+	// LogDir returns the currently configured directory for storing logs.
+	LogDir() string
+}
+
+// Runtime defines the methods that the runtime must implement.
+type Runtime interface {
+	// Logger returns the current logger, if any, in use by the Runtime.
+	// TODO(cnicolaou): remove this.
+	Logger() Logger
+
+	// NewLogger creates a new instance of the logging interface that is
+	// separate from the one provided by Runtime.
+	NewLogger(name string, opts ...LoggingOpts) (Logger, error)
+}
diff --git a/vlog/opts.go b/vlog/opts.go
new file mode 100644
index 0000000..9f5f74b
--- /dev/null
+++ b/vlog/opts.go
@@ -0,0 +1,56 @@
+package vlog
+
+type LoggingOpts interface {
+	LoggingOpt()
+}
+
+type AutoFlush bool
+type AlsoLogToStderr bool
+type LogDir string
+type LogToStderr bool
+type OverridePriorConfiguration bool
+type MaxStackBufSize int
+
+// If true, logs are written to standard error as well as to files.
+func (_ AlsoLogToStderr) LoggingOpt() {}
+
+// Enable V-leveled logging at the specified level.
+func (_ Level) LoggingOpt() {}
+
+// log files will be written to this directory instead of the
+// default temporary directory.
+func (_ LogDir) LoggingOpt() {}
+
+// If true, logs are written to standard error instead of to files.
+func (_ LogToStderr) LoggingOpt() {}
+
+// Set the max size (bytes) of the byte buffer to use for stack
+// traces. The default max is 4M; use powers of 2 since the
+// stack size will be grown exponentially until it exceeds the max.
+// A min of 128K is enforced and any attempts to reduce this will
+// be silently ignored.
+func (_ MaxStackBufSize) LoggingOpt() {}
+
+// The syntax of the argument is a comma-separated list of pattern=N,
+// where pattern is a literal file name (minus the ".go" suffix) or
+// "glob" pattern and N is a V level. For instance,
+//	-gopher*=3
+// sets the V level to 3 in all Go files whose names begin "gopher".
+func (_ ModuleSpec) LoggingOpt() {}
+
+// Log events at or above this severity are logged to standard
+// error as well as to files.
+func (_ StderrThreshold) LoggingOpt() {}
+
+// When set to a file and line number holding a logging statement, such as
+//	gopherflakes.go:234
+// a stack trace will be written to the Info log whenever execution
+// hits that statement. (Unlike with -vmodule, the ".go" must be
+// present.)
+func (_ TraceLocation) LoggingOpt() {}
+
+// If true, enables automatic flushing of log output on every call
+func (_ AutoFlush) LoggingOpt() {}
+
+// If true, allows this call to ConfigureLogger to override a prior configuration.
+func (_ OverridePriorConfiguration) LoggingOpt() {}
diff --git a/vlog/util_test.go b/vlog/util_test.go
new file mode 100644
index 0000000..3b8aa44
--- /dev/null
+++ b/vlog/util_test.go
@@ -0,0 +1,6 @@
+package vlog
+
+// SetLog allows us to override the Log global for testing purposes.
+func SetLog(l Logger) {
+	Log = l.(*logger)
+}
diff --git a/vlog/v23_test.go b/vlog/v23_test.go
new file mode 100644
index 0000000..5de1cfe
--- /dev/null
+++ b/vlog/v23_test.go
@@ -0,0 +1,30 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file was auto-generated via go generate.
+// DO NOT UPDATE MANUALLY
+package vlog_test
+
+import "fmt"
+import "testing"
+import "os"
+
+import "v.io/x/ref/lib/modules"
+import "v.io/x/ref/lib/testutil"
+
+func init() {
+	modules.RegisterChild("child", ``, child)
+}
+
+func TestMain(m *testing.M) {
+	testutil.Init()
+	if modules.IsModulesChildProcess() {
+		if err := modules.Dispatch(); err != nil {
+			fmt.Fprintf(os.Stderr, "modules.Dispatch failed: %v\n", err)
+			os.Exit(1)
+		}
+		return
+	}
+	os.Exit(m.Run())
+}