<commit_before>package main

import (
	"fmt"
	"github.com/jbdalido/smg/Godeps/_workspace/src/github.com/codegangsta/cli"
	log "github.com/jbdalido/smg/Godeps/_workspace/src/github.com/jbdalido/logrus"
	"github.com/jbdalido/smg/engine"
	"github.com/jbdalido/smg/utils"
	"os"
	"os/signal"
)

var (
	smgapp      *engine.Application
	eng         *engine.Engine
	killChannel chan os.Signal
	endChannel  chan error
)

func main() {

	cliApp := cli.App{
		Name:    "smg",
		Usage:   "Run and Build docker - https://smuggler.io",
		Version: "0.4.1",
	}

	cliApp.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "docker, d",
			Value: "",
			Usage: "Docker endpoint",
		},
		cli.StringFlag{
			Name:   "config, c",
			Value:  "~/.smg.yml",
			Usage:  "Config file to use",
			EnvVar: "SMG_CONFIG",
		},
	}

	buildFlags := []cli.Flag{
		cli.StringFlag{
			Name:  "start, s",
			Value: "smg.yml",
			Usage: "Specify a different file to use for your smg run (default: smg.yml)",
		},
		cli.BoolFlag{
			Name:  "no-cache, n",
			Usage: "Disable the use of docker cache during run and build with provided dockerfiles",
		},
		cli.BoolFlag{
			Name:  "verbose, v",
			Usage: "Verbose Mode",
		},
		cli.BoolFlag{
			Name:  "push, p",
			Usage: "Push images after a successful build",
		},
		cli.BoolFlag{
			Name:  "last, l",
			Usage: "Download last image for each build",
		},
		cli.BoolFlag{
			Name:  "delete, D",
			Usage: "Delete images created after a successful build",
		},
		cli.StringSliceFlag{
			Name:  "etcd",
			Value: &cli.StringSlice{},
			Usage: "ETCD Storage http endpoint",
		},
	}

	runFlags := []cli.Flag{
		cli.StringFlag{
			Name:  "start, s",
			Value: "smg.yml",
			Usage: "Specify a different file to use for your smg run (default: smg.yml)",
		},
		cli.BoolFlag{
			Name:  "no-cache, n",
			Usage: "Disable the use of docker cache during run and build with provided dockerfiles",
		},
		cli.BoolFlag{
			Name:  "verbose, v",
			Usage: "Verbose Mode",
		},
		cli.StringFlag{
			Name:  "env, e",
			Value: "default",
			Usage: "Environment (commands or dockerfiles) to use for the run",
		},
		cli.StringSliceFlag{
			Name:  "override, o",
			Value: &cli.StringSlice{},
			Usage: "Environment (commands or dockerfiles) to use for the run",
		},
		cli.BoolFlag{
			Name:  "keepalive, k",
			Usage: "Keep containers alive after a run (successful or not)",
		},
		cli.BoolFlag{
			Name:  "shared-folder, S",
			Usage: "Use a shared-folder with the main container",
		},
	}

	cliApp.HideVersion = true

	cliApp.Commands = []cli.Command{
		cli.Command{
			Name:   "run",
			Usage:  "Run containers with the proper environment",
			Flags:  runFlags,
			Action: CmdRun,
		},
		cli.Command{
			Name:   "build",
			Usage:  "Build against the active git branch of the folder and the build setup of the smg file",
			Flags:  buildFlags,
			Action: CmdBuild,
		},
	}

	// TODO : something is not right here !
	if len(os.Args) == 1 {
		os.Args = append(os.Args, "help")
	}
	cliApp.Run(os.Args)

}

func Init(c *cli.Context) error {

	utils.InitLogger(c.Bool("verbose"))
	killChannel = make(chan os.Signal, 1)
	endChannel = make(chan error)

	// Start by checking if config exist
	cfg, err := engine.NewConfig(c.GlobalString("config"), c.GlobalString("docker"))
	if err != nil {
		return fmt.Errorf("Could not start smuggler with adapter %s: %s", c.GlobalString("docker"), err)
	}

	// Start the engine with the right adapter
	eng, err = engine.New(cfg)
	if err != nil {
		return fmt.Errorf("%s: %s", c.GlobalString("docker"), err)
	}

	// Setup the application
	smgapp := &engine.Application{
		FilePath:      c.String("start"),
		Repository:    cfg.Repository,
		UseDockerfile: !c.Bool("shared-folder"),
		Uptodate:      c.Bool("last"),
		NoCache:       c.Bool("no-cache"),
		KeepAlive:     c.Bool("keepalive"),
	}

	// FIXME : setup overrides
	smgapp.SetOverrides(c.StringSlice("override"))

	// Either if it's a build or a run we need to init smuggler
	err = eng.Init(smgapp)
	if err != nil {
		return fmt.Errorf("Init failed with smuggler file, %s", err)
	}

	// catch the CTRL-C
	signal.Notify(killChannel, os.Interrupt)

	return nil
}

func CmdBuild(c *cli.Context) {
	err := Init(c)
	if err != nil {
		log.Fatalf("%s", err)
	}
	go func() {
		endChannel <- eng.Build(c.Bool("push"), c.Bool("delete"), c.StringSlice("etcd"))
	}()
	select {
	case err := <-endChannel:
		{
			if err != nil {
				log.Fatalf("%s", err)
			}
		}
	case <-killChannel:
		eng.Stop()
	}
}

func CmdRun(c *cli.Context) {
	err := Init(c)
	if err != nil {
		log.Fatalf("%s", err)
	}
	go func() {
		endChannel <- eng.Run(c.String("env"))
	}()
	select {
	case err := <-endChannel:
		{
			if err != nil {
				log.Fatalf("%s", err)
			}
		}
	case <-killChannel:
		eng.Stop()
	}
}
<commit_msg>bump version<commit_after>package main

import (
	"fmt"
	"github.com/jbdalido/smg/Godeps/_workspace/src/github.com/codegangsta/cli"
	log "github.com/jbdalido/smg/Godeps/_workspace/src/github.com/jbdalido/logrus"
	"github.com/jbdalido/smg/engine"
	"github.com/jbdalido/smg/utils"
	"os"
	"os/signal"
)

var (
	smgapp      *engine.Application
	eng         *engine.Engine
	killChannel chan os.Signal
	endChannel  chan error
)

func main() {

	cliApp := cli.App{
		Name:    "smg",
		Usage:   "Run and Build docker - https://smuggler.io",
		Version: "0.5",
	}

	cliApp.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "docker, d",
			Value: "",
			Usage: "Docker endpoint",
		},
		cli.StringFlag{
			Name:   "config, c",
			Value:  "~/.smg.yml",
			Usage:  "Config file to use",
			EnvVar: "SMG_CONFIG",
		},
	}

	buildFlags := []cli.Flag{
		cli.StringFlag{
			Name:  "start, s",
			Value: "smg.yml",
			Usage: "Specify a different file to use for your smg run (default: smg.yml)",
		},
		cli.BoolFlag{
			Name:  "no-cache, n",
			Usage: "Disable the use of docker cache during run and build with provided dockerfiles",
		},
		cli.BoolFlag{
			Name:  "verbose, v",
			Usage: "Verbose Mode",
		},
		cli.BoolFlag{
			Name:  "push, p",
			Usage: "Push images after a successful build",
		},
		cli.BoolFlag{
			Name:  "last, l",
			Usage: "Download last image for each build",
		},
		cli.BoolFlag{
			Name:  "delete, D",
			Usage: "Delete images created after a successful build",
		},
		cli.StringSliceFlag{
			Name:  "etcd",
			Value: &cli.StringSlice{},
			Usage: "ETCD Storage http endpoint",
		},
	}

	runFlags := []cli.Flag{
		cli.StringFlag{
			Name:  "start, s",
			Value: "smg.yml",
			Usage: "Specify a different file to use for your smg run (default: smg.yml)",
		},
		cli.BoolFlag{
			Name:  "no-cache, n",
			Usage: "Disable the use of docker cache during run and build with provided dockerfiles",
		},
		cli.BoolFlag{
			Name:  "verbose, v",
			Usage: "Verbose Mode",
		},
		cli.StringFlag{
			Name:  "env, e",
			Value: "default",
			Usage: "Environment (commands or dockerfiles) to use for the run",
		},
		cli.StringSliceFlag{
			Name:  "override, o",
			Value: &cli.StringSlice{},
			Usage: "Environment (commands or dockerfiles) to use for the run",
		},
		cli.BoolFlag{
			Name:  "keepalive, k",
			Usage: "Keep containers alive after a run (successful or not)",
		},
		cli.BoolFlag{
			Name:  "shared-folder, S",
			Usage: "Use a shared-folder with the main container",
		},
	}

	cliApp.HideVersion = true

	cliApp.Commands = []cli.Command{
		cli.Command{
			Name:   "run",
			Usage:  "Run containers with the proper environment",
			Flags:  runFlags,
			Action: CmdRun,
		},
		cli.Command{
			Name:   "build",
			Usage:  "Build against the active git branch of the folder and the build setup of the smg file",
			Flags:  buildFlags,
			Action: CmdBuild,
		},
	}

	// TODO : something is not right here !
	if len(os.Args) == 1 {
		os.Args = append(os.Args, "help")
	}
	cliApp.Run(os.Args)

}

func Init(c *cli.Context) error {

	utils.InitLogger(c.Bool("verbose"))
	killChannel = make(chan os.Signal, 1)
	endChannel = make(chan error)

	// Start by checking if config exist
	cfg, err := engine.NewConfig(c.GlobalString("config"), c.GlobalString("docker"))
	if err != nil {
		return fmt.Errorf("Could not start smuggler with adapter %s: %s", c.GlobalString("docker"), err)
	}

	// Start the engine with the right adapter
	eng, err = engine.New(cfg)
	if err != nil {
		return fmt.Errorf("%s: %s", c.GlobalString("docker"), err)
	}

	// Setup the application
	smgapp := &engine.Application{
		FilePath:      c.String("start"),
		Repository:    cfg.Repository,
		UseDockerfile: !c.Bool("shared-folder"),
		Uptodate:      c.Bool("last"),
		NoCache:       c.Bool("no-cache"),
		KeepAlive:     c.Bool("keepalive"),
	}

	// FIXME : setup overrides
	smgapp.SetOverrides(c.StringSlice("override"))

	// Either if it's a build or a run we need to init smuggler
	err = eng.Init(smgapp)
	if err != nil {
		return fmt.Errorf("Init failed with smuggler file, %s", err)
	}

	// catch the CTRL-C
	signal.Notify(killChannel, os.Interrupt)

	return nil
}

func CmdBuild(c *cli.Context) {
	err := Init(c)
	if err != nil {
		log.Fatalf("%s", err)
	}
	go func() {
		endChannel <- eng.Build(c.Bool("push"), c.Bool("delete"), c.StringSlice("etcd"))
	}()
	select {
	case err := <-endChannel:
		{
			if err != nil {
				log.Fatalf("%s", err)
			}
		}
	case <-killChannel:
		eng.Stop()
	}
}

func CmdRun(c *cli.Context) {
	err := Init(c)
	if err != nil {
		log.Fatalf("%s", err)
	}
	go func() {
		endChannel <- eng.Run(c.String("env"))
	}()
	select {
	case err := <-endChannel:
		{
			if err != nil {
				log.Fatalf("%s", err)
			}
		}
	case <-killChannel:
		eng.Stop()
	}
}
<|endoftext|>


<commit_before>package sup

import (
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"os"
	"os/user"
	"path/filepath"
	"strings"
	"sync"

	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/agent"
)

// Client is a wrapper over the SSH connection/sessions.
type SSHClient struct {
	conn         *ssh.Client
	sess         *ssh.Session
	user         string
	host         string
	remoteStdin  io.WriteCloser
	remoteStdout io.Reader
	remoteStderr io.Reader
	connOpened   bool
	sessOpened   bool
	running      bool
	env          string //export FOO="bar"; export BAR="baz";
	color        string
}

type ErrConnect struct {
	User   string
	Host   string
	Reason string
}

func (e ErrConnect) Error() string {
	return fmt.Sprintf(`Connect("%v@%v"): %v`, e.User, e.Host, e.Reason)
}

// parseHost parses and normalizes <user>@<host:port> from a given string.
func (c *SSHClient) parseHost(host string) error {
	c.host = host

	// Remove extra "ssh://" schema
	if len(c.host) > 6 && c.host[:6] == "ssh://" {
		c.host = c.host[6:]
	}

	if at := strings.Index(c.host, "@"); at != -1 {
		c.user = c.host[:at]
		c.host = c.host[at+1:]
	}

	// Add default user, if not set
	if c.user == "" {
		u, err := user.Current()
		if err != nil {
			return err
		}
		c.user = u.Username
	}

	if strings.Index(c.host, "/") != -1 {
		return ErrConnect{c.user, c.host, "unexpected slash in the host URL"}
	}

	// Add default port, if not set
	if strings.Index(c.host, ":") == -1 {
		c.host += ":22"
	}

	return nil
}

var initAuthMethodOnce sync.Once
var authMethod ssh.AuthMethod

// initAuthMethod initiates SSH authentication method.
func initAuthMethod() {
	var signers []ssh.Signer

	// If there's a running SSH Agent, try to use its Private keys.
	sock, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK"))
	if err == nil {
		agent := agent.NewClient(sock)
		signers, _ = agent.Signers()
	}

	// Try to read user's SSH private keys form the standard paths.
	files, _ := filepath.Glob(os.Getenv("HOME") + "/.ssh/id_*")
	for _, file := range files {
		if strings.HasSuffix(file, ".pub") {
			continue // Skip public keys.
		}
		data, err := ioutil.ReadFile(file)
		if err != nil {
			continue
		}
		signer, err := ssh.ParsePrivateKey(data)
		if err != nil {
			continue
		}
		signers = append(signers, signer)

	}
	authMethod = ssh.PublicKeys(signers...)
}

// SSHDialFunc can dial an ssh server and return a client
type SSHDialFunc func(net, addr string, config *ssh.ClientConfig) (*ssh.Client, error)

// Connect creates SSH connection to a specified host.
// It expects the host of the form "[ssh://]host[:port]".
func (c *SSHClient) Connect(host string) error {
	return c.ConnectWith(host, ssh.Dial)
}

// ConnectWith creates a SSH connection to a specified host. It will use dialer to establish the
// connection.
// TODO: Split Signers to its own method.
func (c *SSHClient) ConnectWith(host string, dialer SSHDialFunc) error {
	if c.connOpened {
		return fmt.Errorf("Already connected")
	}

	initAuthMethodOnce.Do(initAuthMethod)

	err := c.parseHost(host)
	if err != nil {
		return err
	}

	config := &ssh.ClientConfig{
		User: c.user,
		Auth: []ssh.AuthMethod{
			authMethod,
		},
	}

	c.conn, err = dialer("tcp", c.host, config)
	if err != nil {
		return ErrConnect{c.user, c.host, err.Error()}
	}
	c.connOpened = true

	return nil
}

// Run runs the task.Run command remotely on c.host.
func (c *SSHClient) Run(task *Task) error {
	if c.running {
		return fmt.Errorf("Session already running")
	}
	if c.sessOpened {
		return fmt.Errorf("Session already connected")
	}

	sess, err := c.conn.NewSession()
	if err != nil {
		return err
	}

	c.remoteStdin, err = sess.StdinPipe()
	if err != nil {
		return err
	}

	c.remoteStdout, err = sess.StdoutPipe()
	if err != nil {
		return err
	}

	c.remoteStderr, err = sess.StderrPipe()
	if err != nil {
		return err
	}

	if task.TTY {
		// Set up terminal modes
		modes := ssh.TerminalModes{
			ssh.ECHO:          0,     // disable echoing
			ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
			ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
		}
		// Request pseudo terminal
		if err := sess.RequestPty("xterm", 80, 40, modes); err != nil {
			return ErrTask{task, fmt.Sprintf("request for pseudo terminal failed: %s", err)}
		}
	}

	// Start the remote command.
	if err := sess.Start(c.env + task.Run); err != nil {
		return ErrTask{task, err.Error()}
	}

	c.sess = sess
	c.sessOpened = true
	c.running = true
	return nil
}

// Wait waits until the remote command finishes and exits.
// It closes the SSH session.
func (c *SSHClient) Wait() error {
	if !c.running {
		return fmt.Errorf("Trying to wait on stopped session")
	}

	err := c.sess.Wait()
	c.sess.Close()
	c.running = false
	c.sessOpened = false

	return err
}

// DialThrough will create a new connection from the ssh server sc is connected to. DialThrough is an SSHDialer.
func (sc *SSHClient) DialThrough(net, addr string, config *ssh.ClientConfig) (*ssh.Client, error) {
	conn, err := sc.conn.Dial(net, addr)
	if err != nil {
		return nil, err
	}
	c, chans, reqs, err := ssh.NewClientConn(conn, addr, config)
	if err != nil {
		return nil, err
	}
	return ssh.NewClient(c, chans, reqs), nil

}

// Close closes the underlying SSH connection and session.
func (c *SSHClient) Close() error {
	if c.sessOpened {
		c.sess.Close()
		c.sessOpened = false
	}
	if !c.connOpened {
		return fmt.Errorf("Trying to close the already closed connection")
	}

	err := c.conn.Close()
	c.connOpened = false
	c.running = false

	return err
}

func (c *SSHClient) Stdin() io.WriteCloser {
	return c.remoteStdin
}

func (c *SSHClient) Stderr() io.Reader {
	return c.remoteStderr
}

func (c *SSHClient) Stdout() io.Reader {
	return c.remoteStdout
}

func (c *SSHClient) Prefix() (string, int) {
	host := c.user + "@" + c.host + " | "
	return c.color + host + ResetColor, len(host)
}

func (c *SSHClient) Write(p []byte) (n int, err error) {
	return c.remoteStdin.Write(p)
}

func (c *SSHClient) WriteClose() error {
	return c.remoteStdin.Close()
}

func (c *SSHClient) Signal(sig os.Signal) error {
	if !c.sessOpened {
		return fmt.Errorf("session is not open")
	}

	switch sig {
	case os.Interrupt:
		// TODO: Turns out that .Signal(ssh.SIGHUP) doesn't work for me.
		// Instead, sending \x03 to the remote session works for me,
		// which sounds like something that should be fixed/resolved
		// upstream in the golang.org/x/crypto/ssh pkg.
		// https://github.com/golang/go/issues/4115#issuecomment-66070418
		c.remoteStdin.Write([]byte("\x03"))
		return c.sess.Signal(ssh.SIGINT)
	default:
		return fmt.Errorf("%v not supported", sig)
	}
}
<commit_msg>Specify required HostKeyCallback<commit_after>package sup

import (
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"os"
	"os/user"
	"path/filepath"
	"strings"
	"sync"

	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/agent"
)

// Client is a wrapper over the SSH connection/sessions.
type SSHClient struct {
	conn         *ssh.Client
	sess         *ssh.Session
	user         string
	host         string
	remoteStdin  io.WriteCloser
	remoteStdout io.Reader
	remoteStderr io.Reader
	connOpened   bool
	sessOpened   bool
	running      bool
	env          string //export FOO="bar"; export BAR="baz";
	color        string
}

type ErrConnect struct {
	User   string
	Host   string
	Reason string
}

func (e ErrConnect) Error() string {
	return fmt.Sprintf(`Connect("%v@%v"): %v`, e.User, e.Host, e.Reason)
}

// parseHost parses and normalizes <user>@<host:port> from a given string.
func (c *SSHClient) parseHost(host string) error {
	c.host = host

	// Remove extra "ssh://" schema
	if len(c.host) > 6 && c.host[:6] == "ssh://" {
		c.host = c.host[6:]
	}

	if at := strings.Index(c.host, "@"); at != -1 {
		c.user = c.host[:at]
		c.host = c.host[at+1:]
	}

	// Add default user, if not set
	if c.user == "" {
		u, err := user.Current()
		if err != nil {
			return err
		}
		c.user = u.Username
	}

	if strings.Index(c.host, "/") != -1 {
		return ErrConnect{c.user, c.host, "unexpected slash in the host URL"}
	}

	// Add default port, if not set
	if strings.Index(c.host, ":") == -1 {
		c.host += ":22"
	}

	return nil
}

var initAuthMethodOnce sync.Once
var authMethod ssh.AuthMethod

// initAuthMethod initiates SSH authentication method.
func initAuthMethod() {
	var signers []ssh.Signer

	// If there's a running SSH Agent, try to use its Private keys.
	sock, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK"))
	if err == nil {
		agent := agent.NewClient(sock)
		signers, _ = agent.Signers()
	}

	// Try to read user's SSH private keys form the standard paths.
	files, _ := filepath.Glob(os.Getenv("HOME") + "/.ssh/id_*")
	for _, file := range files {
		if strings.HasSuffix(file, ".pub") {
			continue // Skip public keys.
		}
		data, err := ioutil.ReadFile(file)
		if err != nil {
			continue
		}
		signer, err := ssh.ParsePrivateKey(data)
		if err != nil {
			continue
		}
		signers = append(signers, signer)

	}
	authMethod = ssh.PublicKeys(signers...)
}

// SSHDialFunc can dial an ssh server and return a client
type SSHDialFunc func(net, addr string, config *ssh.ClientConfig) (*ssh.Client, error)

// Connect creates SSH connection to a specified host.
// It expects the host of the form "[ssh://]host[:port]".
func (c *SSHClient) Connect(host string) error {
	return c.ConnectWith(host, ssh.Dial)
}

// ConnectWith creates a SSH connection to a specified host. It will use dialer to establish the
// connection.
// TODO: Split Signers to its own method.
func (c *SSHClient) ConnectWith(host string, dialer SSHDialFunc) error {
	if c.connOpened {
		return fmt.Errorf("Already connected")
	}

	initAuthMethodOnce.Do(initAuthMethod)

	err := c.parseHost(host)
	if err != nil {
		return err
	}

	config := &ssh.ClientConfig{
		User: c.user,
		Auth: []ssh.AuthMethod{
			authMethod,
		},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	}

	c.conn, err = dialer("tcp", c.host, config)
	if err != nil {
		return ErrConnect{c.user, c.host, err.Error()}
	}
	c.connOpened = true

	return nil
}

// Run runs the task.Run command remotely on c.host.
func (c *SSHClient) Run(task *Task) error {
	if c.running {
		return fmt.Errorf("Session already running")
	}
	if c.sessOpened {
		return fmt.Errorf("Session already connected")
	}

	sess, err := c.conn.NewSession()
	if err != nil {
		return err
	}

	c.remoteStdin, err = sess.StdinPipe()
	if err != nil {
		return err
	}

	c.remoteStdout, err = sess.StdoutPipe()
	if err != nil {
		return err
	}

	c.remoteStderr, err = sess.StderrPipe()
	if err != nil {
		return err
	}

	if task.TTY {
		// Set up terminal modes
		modes := ssh.TerminalModes{
			ssh.ECHO:          0,     // disable echoing
			ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
			ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
		}
		// Request pseudo terminal
		if err := sess.RequestPty("xterm", 80, 40, modes); err != nil {
			return ErrTask{task, fmt.Sprintf("request for pseudo terminal failed: %s", err)}
		}
	}

	// Start the remote command.
	if err := sess.Start(c.env + task.Run); err != nil {
		return ErrTask{task, err.Error()}
	}

	c.sess = sess
	c.sessOpened = true
	c.running = true
	return nil
}

// Wait waits until the remote command finishes and exits.
// It closes the SSH session.
func (c *SSHClient) Wait() error {
	if !c.running {
		return fmt.Errorf("Trying to wait on stopped session")
	}

	err := c.sess.Wait()
	c.sess.Close()
	c.running = false
	c.sessOpened = false

	return err
}

// DialThrough will create a new connection from the ssh server sc is connected to. DialThrough is an SSHDialer.
func (sc *SSHClient) DialThrough(net, addr string, config *ssh.ClientConfig) (*ssh.Client, error) {
	conn, err := sc.conn.Dial(net, addr)
	if err != nil {
		return nil, err
	}
	c, chans, reqs, err := ssh.NewClientConn(conn, addr, config)
	if err != nil {
		return nil, err
	}
	return ssh.NewClient(c, chans, reqs), nil

}

// Close closes the underlying SSH connection and session.
func (c *SSHClient) Close() error {
	if c.sessOpened {
		c.sess.Close()
		c.sessOpened = false
	}
	if !c.connOpened {
		return fmt.Errorf("Trying to close the already closed connection")
	}

	err := c.conn.Close()
	c.connOpened = false
	c.running = false

	return err
}

func (c *SSHClient) Stdin() io.WriteCloser {
	return c.remoteStdin
}

func (c *SSHClient) Stderr() io.Reader {
	return c.remoteStderr
}

func (c *SSHClient) Stdout() io.Reader {
	return c.remoteStdout
}

func (c *SSHClient) Prefix() (string, int) {
	host := c.user + "@" + c.host + " | "
	return c.color + host + ResetColor, len(host)
}

func (c *SSHClient) Write(p []byte) (n int, err error) {
	return c.remoteStdin.Write(p)
}

func (c *SSHClient) WriteClose() error {
	return c.remoteStdin.Close()
}

func (c *SSHClient) Signal(sig os.Signal) error {
	if !c.sessOpened {
		return fmt.Errorf("session is not open")
	}

	switch sig {
	case os.Interrupt:
		// TODO: Turns out that .Signal(ssh.SIGHUP) doesn't work for me.
		// Instead, sending \x03 to the remote session works for me,
		// which sounds like something that should be fixed/resolved
		// upstream in the golang.org/x/crypto/ssh pkg.
		// https://github.com/golang/go/issues/4115#issuecomment-66070418
		c.remoteStdin.Write([]byte("\x03"))
		return c.sess.Signal(ssh.SIGINT)
	default:
		return fmt.Errorf("%v not supported", sig)
	}
}
<|endoftext|>


<commit_before>// td4 ride format parser

package main

import (
	"bytes"
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"os"
)

func hasLoop(n int) bool {
	return n>>7 == 1
}

type ControlFlags struct {
	UseMaximumTime             bool
	UseMinimumTime             bool
	SyncWithAdjacentStation    bool
	LeaveIfAnotherTrainArrives bool
	WaitForLoad                bool
	Load                       LoadType
}

func parseControlFlags(n int) *ControlFlags {
	return &ControlFlags{
		UseMaximumTime:             n>>7 == 1,
		UseMinimumTime:             n>>6 == 1,
		SyncWithAdjacentStation:    n>>5 == 1,
		LeaveIfAnotherTrainArrives: n>>4 == 1,
		WaitForLoad:                n>>3 == 1,
		Load:                       LoadType(n & 7),
	}
}

type RideType int
type VehicleType int
type LoadType int
type OperatingMode int
type SegmentType int

const (
	WOODEN_RIDE    RideType = iota
	STAND_UP_STEEL          = iota
	SUSPENDED               = iota
)

const (
	STEEL              VehicleType = iota
	STEEL_BACKWARDS                = iota
	WOODEN_VEHICLE                 = iota
	INVERTED                       = iota
	SUSPENDED_SWINGING             = iota
	LADYBIRD                       = iota
	STAND_UP                       = iota
)

const (
	MODE_NORMAL                  OperatingMode = 0x00
	MODE_CONTINUOUS_CIRCUIT                    = 0x01
	MODE_REVERSE_INCLINE_SHUTTLE               = 0x02
	MODE_POWERED_LAUNCH                        = 0x03
	MODE_SHUTTLE                               = 0x04
	MODE_BOAT_HIRE                             = 0x05
	MODE_UPWARD_LAUNCH                         = 0x06
	MODE_ROTATING_LIFT                         = 0x07
	MODE_STATION_TO_STATION                    = 0x08
)

const (
	LOAD_ONE_QUARTER    = 0
	LOAD_HALF           = 1
	LOAD_THREE_QUARTERS = 2
	LOAD_FULL           = 3
	LOAD_ANY            = 4
)

const (
	IDX_RIDE_TYPE      = 0
	IDX_VEHICLE_TYPE   = 1
	IDX_HAS_LOOP       = 2
	IDX_OPERATING_MODE = 0x06
	IDX_CONTROL_FLAG   = 0x23
	IDX_NUM_TRAINS     = 0x24
	IDX_CARS_PER_TRAIN = 0x25
	IDX_MIN_WAIT_TIME  = 0x26
	IDX_MAX_WAIT_TIME  = 0x27
	IDX_TRACK_DATA     = 0xc4
)

type Ride struct {
	RideType      RideType
	VehicleType   VehicleType
	HasLoop       bool
	OperatingMode OperatingMode
	ControlFlags  *ControlFlags
	NumTrains     int
	CarsPerTrain  int
	MinWaitTime   int
	MaxWaitTime   int
	TrackData     TrackData
}

const (
	ELEM_FLAT                              SegmentType = 0
	ELEM_END_STATION                                   = 0x1
	ELEM_BEGIN_STATION                                 = 0x2
	ELEM_MIDDLE_STATION                                = 0x3
	ELEM_25_DEG_UP                                     = 0x4
	ELEM_60_DEG_UP                                     = 0x5
	ELEM_FLAT_TO_25_DEG_UP                             = 0x6
	ELEM_25_DEG_TO_60_DEG_UP                           = 0x7
	ELEM_60_DEG_TO_25_DEG_UP                           = 0x8
	ELEM_25_DEG_UP_TO_FLAT                             = 0x9
	ELEM_25_DEG_DOWN                                   = 0x0a
	ELEM_60_DEG_DOWN                                   = 0x0b
	ELEM_FLAT_TO_25_DOWN                               = 0x0c
	ELEM_25_DEG_TO_60_DEG_DOWN                         = 0x0d
	ELEM_60_DEG_DOWN_TO_25_DEG_DOWN                    = 0x0e
	ELEM_25_DEG_DOWN_TO_FLAT                           = 0x0f
	ELEM_LEFT_QUARTER_TURN_5_TILES                     = 0x10
	ELEM_RIGHT_QUARTER_TURN_5_TILES                    = 0x11
	ELEM_FLAT_TO_LEFT_BANK                             = 0x12
	ELEM_FLAT_TO_RIGHT_BANK                            = 0x13
	ELEM_LEFT_BANK_TO_FLAT                             = 0x14
	ELEM_RIGHT_BANK_TO_FLAT                            = 0x15
	ELEM_BANKED_LEFT_QUARTER_TURN_5_TILES              = 0x16
	ELEM_BANKED_RIGHT_QUARTER_TURN_5_TILES             = 0x16
	ELEM_BRAKES                                        = 0x63
	ELEM_BOOSTER                                       = 0x64
	ELEM_WATERFALL                                     = 0x70
	ELEM_ON_RIDE_PHOTO                                 = 0x72
	ELEM_END_OF_RIDE                                   = 0xff
)

type TrackElement struct {
	// XXX, add color schemes

	Segment       SegmentType
	ChainLift     bool
	InvertedTrack bool
	Station       bool
	StationNumber int

	// bits 3, 2, 1 and 0 are a magnitude value (0..15) for brake and booster
	// track segments. The value obtained from these four bits is multiplied
	// by 7.6 km/hr = 4.5 mph.
	//
	// We store the value in km/h
	BoostMagnitude float32

	// For RCT2 "Multi Dimensional Coaster", these four bits specify the amount
	// of rotation.
	Rotation int
}

const (
	STATION_ONE   = 0
	STATION_TWO   = 1
	STATION_THREE = 2
	STATION_FOUR  = 3
)

type TrackData struct {
	Elements []TrackElement
}

// Take a compressed byte stream representing a ride and turn it into a Ride
// struct.
func Deserialize(buf io.Reader) *Ride {
	z, err := NewReader(buf)
	if err != nil {
		panic(err)
	}
	var bitbuffer bytes.Buffer
	bitbuffer.ReadFrom(z)
	out := bitbuffer.Bytes()
	for i := 0; i < 500; i++ {
		ds := hex.EncodeToString([]byte{byte(i)})
		fmt.Printf("%s: %s\n", ds, hex.EncodeToString([]byte{out[i]}))
	}
	r := Ride{
		RideType:      RideType(out[IDX_RIDE_TYPE]),
		VehicleType:   VehicleType(out[IDX_VEHICLE_TYPE]),
		HasLoop:       hasLoop(int(out[IDX_HAS_LOOP])),
		OperatingMode: OperatingMode(out[IDX_OPERATING_MODE]),
		ControlFlags:  parseControlFlags(int(out[IDX_CONTROL_FLAG])),
		NumTrains:     int(out[IDX_NUM_TRAINS]),
		CarsPerTrain:  int(out[IDX_CARS_PER_TRAIN]),
		MinWaitTime:   int(out[IDX_MIN_WAIT_TIME]),
		MaxWaitTime:   int(out[IDX_MAX_WAIT_TIME]),
		TrackData:     parseTrackData(out[IDX_TRACK_DATA:]),
	}
	return &r
}

var EndOfRide = errors.New("End of ride")

// Parse the serialized data into a TrackElement struct
// When the end of ride is encountered a EndOfRide error is returned
func parseElement(rawElement []byte) (te TrackElement, e error) {
	if len(rawElement) != 2 {
		return TrackElement{}, errors.New("invalid length for element input")
	}
	te.Segment = SegmentType(rawElement[0])
	if te.Segment == ELEM_END_OF_RIDE {
		return TrackElement{}, EndOfRide
	}
	q := int(rawElement[1])
	te.ChainLift = q>>7 == 1
	te.InvertedTrack = q>>6 == 1
	te.Station = q>>3 == 1
	te.StationNumber = q & 3

	te.BoostMagnitude = float32(q&15) * 7.6
	te.Rotation = (q&15)*45 - 180
	return
}

func parseTrackData(trackData []byte) TrackData {
	td := new(TrackData)
	for i := 0; i < len(trackData); i += 2 {
		elem, err := parseElement(trackData[i : i+2])
		if err != nil {
			if err == EndOfRide {
				break
			}
			panic(err)
		}
		td.Elements = append(td.Elements, elem)
	}
	return *td
}

func main() {
	fmt.Println(readRaptor())
}

func readRaptor() *Ride {
	f, err := os.Open("rides/raptor.td4")
	if err != nil {
		panic(err)
	}
	return Deserialize(f)
}
<commit_msg>add more ride definitions<commit_after>// td4 ride format parser

package main

import (
	"bytes"
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"os"
)

func hasLoop(n int) bool {
	return n>>7 == 1
}

type ControlFlags struct {
	UseMaximumTime             bool
	UseMinimumTime             bool
	SyncWithAdjacentStation    bool
	LeaveIfAnotherTrainArrives bool
	WaitForLoad                bool
	Load                       LoadType
}

func parseControlFlags(n int) *ControlFlags {
	return &ControlFlags{
		UseMaximumTime:             n>>7 == 1,
		UseMinimumTime:             n>>6 == 1,
		SyncWithAdjacentStation:    n>>5 == 1,
		LeaveIfAnotherTrainArrives: n>>4 == 1,
		WaitForLoad:                n>>3 == 1,
		Load:                       LoadType(n & 7),
	}
}

type RideType int
type VehicleType int
type LoadType int
type OperatingMode int
type SegmentType int

const (
	WOODEN_RIDE    RideType = iota
	STAND_UP_STEEL          = iota
	SUSPENDED               = iota
)

const (
	STEEL              VehicleType = iota
	STEEL_BACKWARDS                = iota
	WOODEN_VEHICLE                 = iota
	INVERTED                       = iota
	SUSPENDED_SWINGING             = iota
	LADYBIRD                       = iota
	STAND_UP                       = iota
)

const (
	MODE_NORMAL                  OperatingMode = 0x00
	MODE_CONTINUOUS_CIRCUIT                    = 0x01
	MODE_REVERSE_INCLINE_SHUTTLE               = 0x02
	MODE_POWERED_LAUNCH                        = 0x03
	MODE_SHUTTLE                               = 0x04
	MODE_BOAT_HIRE                             = 0x05
	MODE_UPWARD_LAUNCH                         = 0x06
	MODE_ROTATING_LIFT                         = 0x07
	MODE_STATION_TO_STATION                    = 0x08
)

const (
	LOAD_ONE_QUARTER    = 0
	LOAD_HALF           = 1
	LOAD_THREE_QUARTERS = 2
	LOAD_FULL           = 3
	LOAD_ANY            = 4
)

const (
	IDX_RIDE_TYPE      = 0
	IDX_VEHICLE_TYPE   = 1
	IDX_HAS_LOOP       = 2
	IDX_OPERATING_MODE = 0x06
	IDX_CONTROL_FLAG   = 0x23
	IDX_NUM_TRAINS     = 0x24
	IDX_CARS_PER_TRAIN = 0x25
	IDX_MIN_WAIT_TIME  = 0x26
	IDX_MAX_WAIT_TIME  = 0x27
	IDX_TRACK_DATA     = 0xc4
)

type Ride struct {
	RideType      RideType
	VehicleType   VehicleType
	HasLoop       bool
	OperatingMode OperatingMode
	ControlFlags  *ControlFlags
	NumTrains     int
	CarsPerTrain  int
	MinWaitTime   int
	MaxWaitTime   int
	TrackData     TrackData
}

type TrackSegment struct {
	Type          SegmentType
	ElevationGain int

	// Change in tile position. A positive number indicates after the track
	// segment, we're that many tiles forward. A negative number indicates we
	// moved backwards
	ForwardDelta int

	// How far we moved side to side. Negative numbers indicate the track
	// turned left.
	SidewaysDelta int
}

const (
	ELEM_FLAT                              SegmentType = 0
	ELEM_END_STATION                                   = 0x1
	ELEM_BEGIN_STATION                                 = 0x2
	ELEM_MIDDLE_STATION                                = 0x3
	ELEM_25_DEG_UP                                     = 0x4
	ELEM_60_DEG_UP                                     = 0x5
	ELEM_FLAT_TO_25_DEG_UP                             = 0x6
	ELEM_25_DEG_TO_60_DEG_UP                           = 0x7
	ELEM_60_DEG_TO_25_DEG_UP                           = 0x8
	ELEM_25_DEG_UP_TO_FLAT                             = 0x9
	ELEM_25_DEG_DOWN                                   = 0x0a
	ELEM_60_DEG_DOWN                                   = 0x0b
	ELEM_FLAT_TO_25_DOWN                               = 0x0c
	ELEM_25_DEG_TO_60_DEG_DOWN                         = 0x0d
	ELEM_60_DEG_DOWN_TO_25_DEG_DOWN                    = 0x0e
	ELEM_25_DEG_DOWN_TO_FLAT                           = 0x0f
	ELEM_LEFT_QUARTER_TURN_5_TILES                     = 0x10
	ELEM_RIGHT_QUARTER_TURN_5_TILES                    = 0x11
	ELEM_FLAT_TO_LEFT_BANK                             = 0x12
	ELEM_FLAT_TO_RIGHT_BANK                            = 0x13
	ELEM_LEFT_BANK_TO_FLAT                             = 0x14
	ELEM_RIGHT_BANK_TO_FLAT                            = 0x15
	ELEM_BANKED_LEFT_QUARTER_TURN_5_TILES              = 0x16
	ELEM_BANKED_RIGHT_QUARTER_TURN_5_TILES             = 0x17
	ELEM_LEFT_BANK_TO_25_DEG_UP                        = 0x18
	ELEM_RIGHT_BANK_TO_25_DEG_UP                       = 0x19
	ELEM_25_DEG_UP_TO_LEFT_BANK                        = 0x1a
	ELEM_25_DEG_UP_TO_RIGHT_BANK                       = 0x1b
	ELEM_LEFT_BANK_TO_25_DEG_DOWN                      = 0x1c
	ELEM_RIGHT_BANK_TO_25_DEG_DOWN                     = 0x1d
	ELEM_25_DEG_DOWN_TO_LEFT_BANK                      = 0x1e
	ELEM_25_DEG_DOWN_TO_RIGHT_BANK                     = 0x1f

	// Not banked, just turning and sliding downward
	ELEM_LEFT_QUARTER_TURN_5_TILES_25_DEG_DOWN  = 0x24
	ELEM_RIGHT_QUARTER_TURN_5_TILES_25_DEG_DOWN = 0x25

	ELEM_LEFT_QUARTER_TURN_3_TILES_BANK = 0x2c

	ELEM_LEFT_QUARTER_TURN_3_TILES_25_DEG_DOWN = 0x30

	ELEM_RIGHT_TWIST_UP_TO_DOWN = 0x35

	ELEM_RIGHT_TWIST_UP_TO_DOWN = 0x37

	ELEM_LEFT_CORKSCREW_UP    = 0x3a
	ELEM_RIGHT_CORKSCREW_UP   = 0x3b
	ELEM_LEFT_CORKSCREW_DOWN  = 0x3c
	ELEM_RIGHT_CORKSCREW_DOWN = 0x3d

	ELEM_BRAKES  = 0x63
	ELEM_BOOSTER = 0x64
	// This should only be used in RCT2, I think.
	ELEM_INVERTED_90_DEG_UP_TO_FLAT_QUARTER_LOOP = 0x65
	ELEM_LEFT_QUARTER_BANKED_HELIX_LARGE_UP      = 0x66
	ELEM_RIGHT_QUARTER_BANKED_HELIX_LARGE_UP     = 0x67
	ELEM_LEFT_QUARTER_BANKED_HELIX_LARGE_DOWN    = 0x68
	ELEM_RIGHT_QUARTER_BANKED_HELIX_LARGE_DOWN   = 0x69
	ELEM_WATERFALL                               = 0x70
	ELEM_ON_RIDE_PHOTO                           = 0x72

	ELEM_RIGHT_EIGHTH_BANK_TO_DIAG      = 0x8a
	ELEM_LEFT_EIGHTH_BANK_TO_ORTHOGONAL = 0x8b

	ELEM_DIAG_25_DEG_UP = 0x8e

	ELEM_DIAG_25_DEG_TO_FLAT             = 0x93
	ELEM_DIAG_FLAT_TO_25_DEG_DOWN        = 0x96
	ELEM_DIAG_25_DEG_DOWN_TO_60_DEG_DOWN = 0x97
	ELEM_DIAG_60_DEG_DOWN_TO_25_DEG_DOWN = 0x98
	ELEM_DIAG_25_DEG_DOWN_TO_FLAT        = 0x99

	ELEM_DIAG_FLAT_TO_LEFT_BANK = 0x9e

	ELEM_DIAG_RIGHT_BANK_TO_25_DEG_UP = 0xa3

	ELEM_RIGHT_LARGE_HALF_LOOP_UP   = 0xb8
	ELEM_RIGHT_LARGE_HALF_LOOP_DOWN = 0xb9

	ELEM_LEFT_LARGE_HALF_LOOP_DOWN = 0xba

	ELEM_END_OF_RIDE = 0xff
)

type TrackElement struct {
	// XXX, add color schemes

	Segment       SegmentType
	ChainLift     bool
	InvertedTrack bool
	Station       bool
	StationNumber int

	// bits 3, 2, 1 and 0 are a magnitude value (0..15) for brake and booster
	// track segments. The value obtained from these four bits is multiplied
	// by 7.6 km/hr = 4.5 mph.
	//
	// We store the value in km/h
	BoostMagnitude float32

	// For RCT2 "Multi Dimensional Coaster", these four bits specify the amount
	// of rotation.
	Rotation int
}

func (te TrackElement) String() string {
	if te.Segment > 0x17 {
		return hex.EncodeToString([]byte{byte(te.Segment)})
	}
	return ""
}

const (
	STATION_ONE   = 0
	STATION_TWO   = 1
	STATION_THREE = 2
	STATION_FOUR  = 3
)

type TrackData struct {
	Elements []TrackElement
}

// Take a compressed byte stream representing a ride and turn it into a Ride
// struct.
func Deserialize(buf io.Reader) *Ride {
	z, err := NewReader(buf)
	if err != nil {
		panic(err)
	}
	var bitbuffer bytes.Buffer
	bitbuffer.ReadFrom(z)
	out := bitbuffer.Bytes()
	for i := 0; i < 500; i++ {
		ds := hex.EncodeToString([]byte{byte(i)})
		fmt.Printf("%s: %s\n", ds, hex.EncodeToString([]byte{out[i]}))
	}
	r := Ride{
		RideType:      RideType(out[IDX_RIDE_TYPE]),
		VehicleType:   VehicleType(out[IDX_VEHICLE_TYPE]),
		HasLoop:       hasLoop(int(out[IDX_HAS_LOOP])),
		OperatingMode: OperatingMode(out[IDX_OPERATING_MODE]),
		ControlFlags:  parseControlFlags(int(out[IDX_CONTROL_FLAG])),
		NumTrains:     int(out[IDX_NUM_TRAINS]),
		CarsPerTrain:  int(out[IDX_CARS_PER_TRAIN]),
		MinWaitTime:   int(out[IDX_MIN_WAIT_TIME]),
		MaxWaitTime:   int(out[IDX_MAX_WAIT_TIME]),
		TrackData:     parseTrackData(out[IDX_TRACK_DATA:]),
	}
	return &r
}

var EndOfRide = errors.New("End of ride")

// Parse the serialized data into a TrackElement struct
// When the end of ride is encountered a EndOfRide error is returned
func parseElement(rawElement []byte) (te TrackElement, e error) {
	if len(rawElement) != 2 {
		return TrackElement{}, errors.New("invalid length for element input")
	}
	te.Segment = SegmentType(rawElement[0])
	if te.Segment == ELEM_END_OF_RIDE {
		return TrackElement{}, EndOfRide
	}
	q := int(rawElement[1])
	te.ChainLift = q>>7 == 1
	te.InvertedTrack = q>>6 == 1
	te.Station = q>>3 == 1
	te.StationNumber = q & 3

	te.BoostMagnitude = float32(q&15) * 7.6
	te.Rotation = (q&15)*45 - 180
	return
}

func parseTrackData(trackData []byte) TrackData {
	td := new(TrackData)
	for i := 0; i < len(trackData); i += 2 {
		elem, err := parseElement(trackData[i : i+2])
		if err != nil {
			if err == EndOfRide {
				break
			}
			panic(err)
		}
		td.Elements = append(td.Elements, elem)
	}
	return *td
}

func main() {
	fmt.Println(readRaptor())
}

func readRaptor() *Ride {
	f, err := os.Open("rides/raptor.td4")
	if err != nil {
		panic(err)
	}
	return Deserialize(f)
}
<|endoftext|>


<commit_before>// +build linux

package main

import (
	"fmt"
	"io"
	"os"
	"os/signal"
	"sync"

	"github.com/containerd/console"
	"github.com/opencontainers/runc/libcontainer"
	"github.com/opencontainers/runc/libcontainer/utils"
	"github.com/pkg/errors"
)

type tty struct {
	epoller     *console.Epoller
	console     *console.EpollConsole
	hostConsole console.Console
	closers     []io.Closer
	postStart   []io.Closer
	wg          sync.WaitGroup
	consoleC    chan error
}

func (t *tty) copyIO(w io.Writer, r io.ReadCloser) {
	defer t.wg.Done()
	_, _ = io.Copy(w, r)
	_ = r.Close()
}

// setup pipes for the process so that advanced features like c/r are able to easily checkpoint
// and restore the process's IO without depending on a host specific path or device
func setupProcessPipes(p *libcontainer.Process, rootuid, rootgid int) (*tty, error) {
	i, err := p.InitializeIO(rootuid, rootgid)
	if err != nil {
		return nil, err
	}
	t := &tty{
		closers: []io.Closer{
			i.Stdin,
			i.Stdout,
			i.Stderr,
		},
	}
	// add the process's io to the post start closers if they support close
	for _, cc := range []interface{}{
		p.Stdin,
		p.Stdout,
		p.Stderr,
	} {
		if c, ok := cc.(io.Closer); ok {
			t.postStart = append(t.postStart, c)
		}
	}
	go func() {
		_, _ = io.Copy(i.Stdin, os.Stdin)
		_ = i.Stdin.Close()
	}()
	t.wg.Add(2)
	go t.copyIO(os.Stdout, i.Stdout)
	go t.copyIO(os.Stderr, i.Stderr)
	return t, nil
}

func inheritStdio(process *libcontainer.Process) error {
	process.Stdin = os.Stdin
	process.Stdout = os.Stdout
	process.Stderr = os.Stderr
	return nil
}

func (t *tty) initHostConsole() error {
	// Usually all three (stdin, stdout, and stderr) streams are open to
	// the terminal, but they might be redirected, so try them all.
	for _, s := range []*os.File{os.Stderr, os.Stdout, os.Stdin} {
		c, err := console.ConsoleFromFile(s)
		switch err {
		case nil:
			t.hostConsole = c
			return nil
		case console.ErrNotAConsole:
			continue
		default:
			// should not happen
			return errors.Wrap(err, "unable to get console")
		}
	}
	// If all streams are redirected, but we still have a controlling
	// terminal, it can be obtained by opening /dev/tty.
	tty, err := os.Open("/dev/tty")
	if err != nil {
		return err
	}
	c, err := console.ConsoleFromFile(tty)
	if err != nil {
		return errors.Wrap(err, "unable to get console")
	}

	t.hostConsole = c
	return nil
}

func (t *tty) recvtty(process *libcontainer.Process, socket *os.File) (Err error) {
	f, err := utils.RecvFd(socket)
	if err != nil {
		return err
	}
	cons, err := console.ConsoleFromFile(f)
	if err != nil {
		return err
	}
	err = console.ClearONLCR(cons.Fd())
	if err != nil {
		return err
	}
	epoller, err := console.NewEpoller()
	if err != nil {
		return err
	}
	epollConsole, err := epoller.Add(cons)
	if err != nil {
		return err
	}
	defer func() {
		if Err != nil {
			_ = epollConsole.Close()
		}
	}()
	go func() { _ = epoller.Wait() }()
	go func() { _, _ = io.Copy(epollConsole, os.Stdin) }()
	t.wg.Add(1)
	go t.copyIO(os.Stdout, epollConsole)

	// Set raw mode for the controlling terminal.
	if err := t.hostConsole.SetRaw(); err != nil {
		return fmt.Errorf("failed to set the terminal from the stdin: %w", err)
	}
	go handleInterrupt(t.hostConsole)

	t.epoller = epoller
	t.console = epollConsole
	t.closers = []io.Closer{epollConsole}
	return nil
}

func handleInterrupt(c console.Console) {
	sigchan := make(chan os.Signal, 1)
	signal.Notify(sigchan, os.Interrupt)
	<-sigchan
	_ = c.Reset()
	os.Exit(0)
}

func (t *tty) waitConsole() error {
	if t.consoleC != nil {
		return <-t.consoleC
	}
	return nil
}

// ClosePostStart closes any fds that are provided to the container and dup2'd
// so that we no longer have copy in our process.
func (t *tty) ClosePostStart() error {
	for _, c := range t.postStart {
		_ = c.Close()
	}
	return nil
}

// Close closes all open fds for the tty and/or restores the original
// stdin state to what it was prior to the container execution
func (t *tty) Close() error {
	// ensure that our side of the fds are always closed
	for _, c := range t.postStart {
		_ = c.Close()
	}
	// the process is gone at this point, shutting down the console if we have
	// one and wait for all IO to be finished
	if t.console != nil && t.epoller != nil {
		_ = t.console.Shutdown(t.epoller.CloseConsole)
	}
	t.wg.Wait()
	for _, c := range t.closers {
		_ = c.Close()
	}
	if t.hostConsole != nil {
		_ = t.hostConsole.Reset()
	}
	return nil
}

func (t *tty) resize() error {
	if t.console == nil || t.hostConsole == nil {
		return nil
	}
	return t.console.ResizeFrom(t.hostConsole)
}
<commit_msg>tty.go: don't use pkg/errors, use errors.Is<commit_after>// +build linux

package main

import (
	"errors"
	"fmt"
	"io"
	"os"
	"os/signal"
	"sync"

	"github.com/containerd/console"
	"github.com/opencontainers/runc/libcontainer"
	"github.com/opencontainers/runc/libcontainer/utils"
)

type tty struct {
	epoller     *console.Epoller
	console     *console.EpollConsole
	hostConsole console.Console
	closers     []io.Closer
	postStart   []io.Closer
	wg          sync.WaitGroup
	consoleC    chan error
}

func (t *tty) copyIO(w io.Writer, r io.ReadCloser) {
	defer t.wg.Done()
	_, _ = io.Copy(w, r)
	_ = r.Close()
}

// setup pipes for the process so that advanced features like c/r are able to easily checkpoint
// and restore the process's IO without depending on a host specific path or device
func setupProcessPipes(p *libcontainer.Process, rootuid, rootgid int) (*tty, error) {
	i, err := p.InitializeIO(rootuid, rootgid)
	if err != nil {
		return nil, err
	}
	t := &tty{
		closers: []io.Closer{
			i.Stdin,
			i.Stdout,
			i.Stderr,
		},
	}
	// add the process's io to the post start closers if they support close
	for _, cc := range []interface{}{
		p.Stdin,
		p.Stdout,
		p.Stderr,
	} {
		if c, ok := cc.(io.Closer); ok {
			t.postStart = append(t.postStart, c)
		}
	}
	go func() {
		_, _ = io.Copy(i.Stdin, os.Stdin)
		_ = i.Stdin.Close()
	}()
	t.wg.Add(2)
	go t.copyIO(os.Stdout, i.Stdout)
	go t.copyIO(os.Stderr, i.Stderr)
	return t, nil
}

func inheritStdio(process *libcontainer.Process) error {
	process.Stdin = os.Stdin
	process.Stdout = os.Stdout
	process.Stderr = os.Stderr
	return nil
}

func (t *tty) initHostConsole() error {
	// Usually all three (stdin, stdout, and stderr) streams are open to
	// the terminal, but they might be redirected, so try them all.
	for _, s := range []*os.File{os.Stderr, os.Stdout, os.Stdin} {
		c, err := console.ConsoleFromFile(s)
		if err == nil {
			t.hostConsole = c
			return nil
		}
		if errors.Is(err, console.ErrNotAConsole) {
			continue
		}
		// should not happen
		return fmt.Errorf("unable to get console: %w", err)
	}
	// If all streams are redirected, but we still have a controlling
	// terminal, it can be obtained by opening /dev/tty.
	tty, err := os.Open("/dev/tty")
	if err != nil {
		return err
	}
	c, err := console.ConsoleFromFile(tty)
	if err != nil {
		return fmt.Errorf("unable to get console: %w", err)
	}

	t.hostConsole = c
	return nil
}

func (t *tty) recvtty(process *libcontainer.Process, socket *os.File) (Err error) {
	f, err := utils.RecvFd(socket)
	if err != nil {
		return err
	}
	cons, err := console.ConsoleFromFile(f)
	if err != nil {
		return err
	}
	err = console.ClearONLCR(cons.Fd())
	if err != nil {
		return err
	}
	epoller, err := console.NewEpoller()
	if err != nil {
		return err
	}
	epollConsole, err := epoller.Add(cons)
	if err != nil {
		return err
	}
	defer func() {
		if Err != nil {
			_ = epollConsole.Close()
		}
	}()
	go func() { _ = epoller.Wait() }()
	go func() { _, _ = io.Copy(epollConsole, os.Stdin) }()
	t.wg.Add(1)
	go t.copyIO(os.Stdout, epollConsole)

	// Set raw mode for the controlling terminal.
	if err := t.hostConsole.SetRaw(); err != nil {
		return fmt.Errorf("failed to set the terminal from the stdin: %w", err)
	}
	go handleInterrupt(t.hostConsole)

	t.epoller = epoller
	t.console = epollConsole
	t.closers = []io.Closer{epollConsole}
	return nil
}

func handleInterrupt(c console.Console) {
	sigchan := make(chan os.Signal, 1)
	signal.Notify(sigchan, os.Interrupt)
	<-sigchan
	_ = c.Reset()
	os.Exit(0)
}

func (t *tty) waitConsole() error {
	if t.consoleC != nil {
		return <-t.consoleC
	}
	return nil
}

// ClosePostStart closes any fds that are provided to the container and dup2'd
// so that we no longer have copy in our process.
func (t *tty) ClosePostStart() error {
	for _, c := range t.postStart {
		_ = c.Close()
	}
	return nil
}

// Close closes all open fds for the tty and/or restores the original
// stdin state to what it was prior to the container execution
func (t *tty) Close() error {
	// ensure that our side of the fds are always closed
	for _, c := range t.postStart {
		_ = c.Close()
	}
	// the process is gone at this point, shutting down the console if we have
	// one and wait for all IO to be finished
	if t.console != nil && t.epoller != nil {
		_ = t.console.Shutdown(t.epoller.CloseConsole)
	}
	t.wg.Wait()
	for _, c := range t.closers {
		_ = c.Close()
	}
	if t.hostConsole != nil {
		_ = t.hostConsole.Reset()
	}
	return nil
}

func (t *tty) resize() error {
	if t.console == nil || t.hostConsole == nil {
		return nil
	}
	return t.console.ResizeFrom(t.hostConsole)
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"os"
	"syscall"
	"unsafe"
)

/*** defines ***/

const KILO_VERSION = "0.0.1"
const (
	ARROW_LEFT  = 1000 + iota
	ARROW_RIGHT = 1000 + iota
	ARROW_UP    = 1000 + iota
	ARROW_DOWN  = 1000 + iota
	DEL_KEY     = 1000 + iota
	HOME_KEY    = 1000 + iota
	END_KEY     = 1000 + iota
	PAGE_UP     = 1000 + iota
	PAGE_DOWN   = 1000 + iota
)

/*** data ***/

type Termios struct {
	Iflag  uint32
	Oflag  uint32
	Cflag  uint32
	Lflag  uint32
	Cc     [20]byte
	Ispeed uint32
	Ospeed uint32
}

type erow struct {
	chars []byte
	size  int
}

type editorConfig struct {
	cx          int
	cy          int
	rowoff      int
	coloff      int
	screenRows  int
	screenCols  int
	numRows     int
	rows        []erow
	origTermios *Termios
}

type WinSize struct {
	Row    uint16
	Col    uint16
	Xpixel uint16
	Ypixel uint16
}

var E editorConfig

/*** terminal ***/

func die(err error) {
	disableRawMode()
	io.WriteString(os.Stdout, "\x1b[2J")
	io.WriteString(os.Stdout, "\x1b[H")
	log.Fatal(err)
}

func TcSetAttr(fd uintptr, termios *Termios) error {
	// TCSETS+1 == TCSETSW, because TCSAFLUSH doesn't exist
	if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TCSETS+1), uintptr(unsafe.Pointer(termios))); err != 0 {
		return err
	}
	return nil
}

func TcGetAttr(fd uintptr) *Termios {
	var termios = &Termios{}
	if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, syscall.TCGETS, uintptr(unsafe.Pointer(termios))); err != 0 {
		log.Fatalf("Problem getting terminal attributes: %s\n", err)
	}
	return termios
}

func enableRawMode() {
	E.origTermios = TcGetAttr(os.Stdin.Fd())
	var raw Termios
	raw = *E.origTermios
	raw.Iflag &^= syscall.BRKINT | syscall.ICRNL | syscall.INPCK | syscall.ISTRIP | syscall.IXON
	raw.Oflag &^= syscall.OPOST
	raw.Cflag |= syscall.CS8
	raw.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.IEXTEN | syscall.ISIG
	raw.Cc[syscall.VMIN+1] = 0
	raw.Cc[syscall.VTIME+1] = 1
	if e := TcSetAttr(os.Stdin.Fd(), &raw); e != nil {
		log.Fatalf("Problem enabling raw mode: %s\n", e)
	}
}

func disableRawMode() {
	if e := TcSetAttr(os.Stdin.Fd(), E.origTermios); e != nil {
		log.Fatalf("Problem disabling raw mode: %s\n", e)
	}
}

func editorReadKey() int {
	var buffer [1]byte
	var cc int
	var err error
	for cc, err = os.Stdin.Read(buffer[:]); cc != 1; cc, err = os.Stdin.Read(buffer[:]) {
	}
	if err != nil {
		die(err)
	}
	if buffer[0] == '\x1b' {
		var seq [2]byte
		if cc, _ = os.Stdin.Read(seq[:]); cc != 2 {
			return '\x1b'
		}

		if seq[0] == '[' {
			if seq[1] >= '0' && seq[1] <= '9' {
				if cc, err = os.Stdin.Read(buffer[:]); cc != 1 {
					return '\x1b'
				}
				if buffer[0] == '~' {
					switch seq[1] {
					case '1':
						return HOME_KEY
					case '3':
						return DEL_KEY
					case '4':
						return END_KEY
					case '5':
						return PAGE_UP
					case '6':
						return PAGE_DOWN
					case '7':
						return HOME_KEY
					case '8':
						return END_KEY
					}
				}
				// XXX - what happens here?
			} else {
				switch seq[1] {
				case 'A':
					return ARROW_UP
				case 'B':
					return ARROW_DOWN
				case 'C':
					return ARROW_RIGHT
				case 'D':
					return ARROW_LEFT
				case 'H':
					return HOME_KEY
				case 'F':
					return END_KEY
				}
			}
		} else if seq[0] == '0' {
			switch seq[1] {
			case 'H':
				return HOME_KEY
			case 'F':
				return END_KEY
			}
		}

		return '\x1b'
	}
	return int(buffer[0])
}

func getCursorPosition(rows *int, cols *int) int {
	io.WriteString(os.Stdout, "\x1b[6n")
	var buffer [1]byte
	var buf []byte
	var cc int
	for cc, _ = os.Stdin.Read(buffer[:]); cc == 1; cc, _ = os.Stdin.Read(buffer[:]) {
		if buffer[0] == 'R' {
			break
		}
		buf = append(buf, buffer[0])
	}
	if string(buf[0:2]) != "\x1b[" {
		log.Printf("Failed to read rows;cols from tty\n")
		return -1
	}
	if n, e := fmt.Sscanf(string(buf[2:]), "%d;%d", rows, cols); n != 2 || e != nil {
		if e != nil {
			log.Printf("getCursorPosition: fmt.Sscanf() failed: %s\n", e)
		}
		if n != 2 {
			log.Printf("getCursorPosition: got %d items, wanted 2\n", n)
		}
		return -1
	}
	return 0
}

func getWindowSize(rows *int, cols *int) int {
	var w WinSize
	_, _, err := syscall.Syscall(syscall.SYS_IOCTL,
		os.Stdout.Fd(),
		syscall.TIOCGWINSZ,
		uintptr(unsafe.Pointer(&w)),
	)
	if err != 0 { // type syscall.Errno
		io.WriteString(os.Stdout, "\x1b[999C\x1b[999B")
		return getCursorPosition(rows, cols)
	} else {
		*rows = int(w.Row)
		*cols = int(w.Col)
		return 0
	}
	return -1
}

/*** row operations ***/

func editorAppendRow(s []byte) {
	var r erow
	r.chars = s
	r.size = len(s)
	E.rows = append(E.rows, r)
	E.numRows++
}

/*** file I/O ***/

func editorOpen(filename string) {
	fd, err := os.Open(filename)
	if err != nil {
		die(err)
	}
	defer fd.Close()
	fp := bufio.NewReader(fd)

	for line, err := fp.ReadBytes('\n'); err == nil; line, err = fp.ReadBytes('\n') {
		// Trim trailing newlines and carriage returns
		for c := line[len(line) - 1]; len(line) > 0 && (c == '\n' || c == '\r'); {
			line = line[:len(line)-1]
			if len(line) > 0 {
				c = line[len(line) - 1]
			}
		}
		editorAppendRow(line)
	}

	if err != nil && err != io.EOF {
		die(err)
	}
}

/*** input ***/

func editorMoveCursor(key int) {
	switch key {
	case ARROW_LEFT:
		if E.cx != 0 {
			E.cx--
		} else if E.cy > 0 {
			E.cy--
			E.cx = E.rows[E.cy].size
		}
	case ARROW_RIGHT:
		if E.cy < E.numRows && E.cx < E.rows[E.cy].size {
			E.cx++
		}
	case ARROW_UP:
		if E.cy != 0 {
			E.cy--
		}
	case ARROW_DOWN:
		if E.cy < E.numRows {
			E.cy++
		}
	}

	rowlen := 0
	if E.cy < E.numRows {
		rowlen = E.rows[E.cy].size
	}
	if E.cx > rowlen {
		E.cx = rowlen
	}
}

func editorProcessKeypress() {
	c := editorReadKey()
	switch c {
	case ('q' & 0x1f):
		io.WriteString(os.Stdout, "\x1b[2J")
		io.WriteString(os.Stdout, "\x1b[H")
		disableRawMode()
		os.Exit(0)
	case HOME_KEY:
		E.cx = 0
	case END_KEY:
		E.cx = E.screenCols - 1
	case PAGE_UP, PAGE_DOWN:
		dir := ARROW_DOWN
		if c == PAGE_UP {
			dir = ARROW_UP
		}
		for times := E.screenRows; times > 0; times-- {
			editorMoveCursor(dir)
		}
	case ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT:
		editorMoveCursor(c)
	}
}

/*** append buffer ***/

type abuf struct {
	buf []byte
}

func (p abuf) String() string {
	return string(p.buf)
}

func (p *abuf) abAppend(s string) {
	p.buf = append(p.buf, []byte(s)...)
}

func (p *abuf) abAppendBytes(b []byte) {
	p.buf = append(p.buf, b...)
}

/*** output ***/

func editorScroll() {
	if E.cy < E.rowoff {
		E.rowoff = E.cy
	}
	if E.cy >= E.rowoff + E.screenRows {
		E.rowoff = E.cy - E.screenRows + 1
	}
	if E.cx < E.coloff {
		E.coloff = E.cx
	}
	if E.cx >= E.coloff + E.screenCols {
		E.coloff = E.cx - E.screenCols + 1
	}
}

func editorRefreshScreen() {
	editorScroll()
	var ab abuf
	ab.abAppend("\x1b[25l")
	ab.abAppend("\x1b[H")
	editorDrawRows(&ab)
	ab.abAppend(fmt.Sprintf("\x1b[%d;%dH", (E.cy - E.rowoff) + 1, (E.cx - E.coloff) + 1))
	ab.abAppend("\x1b[25h")
	_, e := io.WriteString(os.Stdout, ab.String())
	if e != nil {
		log.Fatal(e)
	}
}

func editorDrawRows(ab *abuf) {
	for y := 0; y < E.screenRows; y++ {
		filerow := y + E.rowoff
		if filerow >= E.numRows {
			if E.numRows == 0 && y == E.screenRows/3 {
				w := fmt.Sprintf("Kilo editor -- version %s", KILO_VERSION)
				if len(w) > E.screenCols {
					w = w[0:E.screenCols]
				}
				pad := "~ "
				for padding := (E.screenCols - len(w)) / 2; padding > 0; padding-- {
					ab.abAppend(pad)
					pad = " "
				}
				ab.abAppend(w)
			} else {
				ab.abAppend("~")
			}
		} else {
			len := E.rows[filerow].size - E.coloff
			if len < 0 { len = 0 }
			if len > E.screenCols { len = E.screenCols }
			ab.abAppendBytes(E.rows[filerow].chars[E.coloff:E.coloff+len])
		}
		ab.abAppend("\x1b[K")
		if y < E.screenRows-1 {
			ab.abAppend("\r\n")
		}
	}
}

/*** init ***/

func initEditor() {
	// Initialization a la C not necessary.
	if getWindowSize(&E.screenRows, &E.screenCols) == -1 {
		die(fmt.Errorf("couldn't get screen size"))
	}
}

func main() {
	enableRawMode()
	defer disableRawMode()
	initEditor()
	if len(os.Args) > 1 {
		editorOpen(os.Args[1])
	}

	for {
		editorRefreshScreen()
		editorProcessKeypress()
	}
}
<commit_msg>Step 79<commit_after>package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"os"
	"syscall"
	"unsafe"
)

/*** defines ***/

const KILO_VERSION = "0.0.1"
const (
	ARROW_LEFT  = 1000 + iota
	ARROW_RIGHT = 1000 + iota
	ARROW_UP    = 1000 + iota
	ARROW_DOWN  = 1000 + iota
	DEL_KEY     = 1000 + iota
	HOME_KEY    = 1000 + iota
	END_KEY     = 1000 + iota
	PAGE_UP     = 1000 + iota
	PAGE_DOWN   = 1000 + iota
)

/*** data ***/

type Termios struct {
	Iflag  uint32
	Oflag  uint32
	Cflag  uint32
	Lflag  uint32
	Cc     [20]byte
	Ispeed uint32
	Ospeed uint32
}

type erow struct {
	chars []byte
	size  int
}

type editorConfig struct {
	cx          int
	cy          int
	rowoff      int
	coloff      int
	screenRows  int
	screenCols  int
	numRows     int
	rows        []erow
	origTermios *Termios
}

type WinSize struct {
	Row    uint16
	Col    uint16
	Xpixel uint16
	Ypixel uint16
}

var E editorConfig

/*** terminal ***/

func die(err error) {
	disableRawMode()
	io.WriteString(os.Stdout, "\x1b[2J")
	io.WriteString(os.Stdout, "\x1b[H")
	log.Fatal(err)
}

func TcSetAttr(fd uintptr, termios *Termios) error {
	// TCSETS+1 == TCSETSW, because TCSAFLUSH doesn't exist
	if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TCSETS+1), uintptr(unsafe.Pointer(termios))); err != 0 {
		return err
	}
	return nil
}

func TcGetAttr(fd uintptr) *Termios {
	var termios = &Termios{}
	if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, syscall.TCGETS, uintptr(unsafe.Pointer(termios))); err != 0 {
		log.Fatalf("Problem getting terminal attributes: %s\n", err)
	}
	return termios
}

func enableRawMode() {
	E.origTermios = TcGetAttr(os.Stdin.Fd())
	var raw Termios
	raw = *E.origTermios
	raw.Iflag &^= syscall.BRKINT | syscall.ICRNL | syscall.INPCK | syscall.ISTRIP | syscall.IXON
	raw.Oflag &^= syscall.OPOST
	raw.Cflag |= syscall.CS8
	raw.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.IEXTEN | syscall.ISIG
	raw.Cc[syscall.VMIN+1] = 0
	raw.Cc[syscall.VTIME+1] = 1
	if e := TcSetAttr(os.Stdin.Fd(), &raw); e != nil {
		log.Fatalf("Problem enabling raw mode: %s\n", e)
	}
}

func disableRawMode() {
	if e := TcSetAttr(os.Stdin.Fd(), E.origTermios); e != nil {
		log.Fatalf("Problem disabling raw mode: %s\n", e)
	}
}

func editorReadKey() int {
	var buffer [1]byte
	var cc int
	var err error
	for cc, err = os.Stdin.Read(buffer[:]); cc != 1; cc, err = os.Stdin.Read(buffer[:]) {
	}
	if err != nil {
		die(err)
	}
	if buffer[0] == '\x1b' {
		var seq [2]byte
		if cc, _ = os.Stdin.Read(seq[:]); cc != 2 {
			return '\x1b'
		}

		if seq[0] == '[' {
			if seq[1] >= '0' && seq[1] <= '9' {
				if cc, err = os.Stdin.Read(buffer[:]); cc != 1 {
					return '\x1b'
				}
				if buffer[0] == '~' {
					switch seq[1] {
					case '1':
						return HOME_KEY
					case '3':
						return DEL_KEY
					case '4':
						return END_KEY
					case '5':
						return PAGE_UP
					case '6':
						return PAGE_DOWN
					case '7':
						return HOME_KEY
					case '8':
						return END_KEY
					}
				}
				// XXX - what happens here?
			} else {
				switch seq[1] {
				case 'A':
					return ARROW_UP
				case 'B':
					return ARROW_DOWN
				case 'C':
					return ARROW_RIGHT
				case 'D':
					return ARROW_LEFT
				case 'H':
					return HOME_KEY
				case 'F':
					return END_KEY
				}
			}
		} else if seq[0] == '0' {
			switch seq[1] {
			case 'H':
				return HOME_KEY
			case 'F':
				return END_KEY
			}
		}

		return '\x1b'
	}
	return int(buffer[0])
}

func getCursorPosition(rows *int, cols *int) int {
	io.WriteString(os.Stdout, "\x1b[6n")
	var buffer [1]byte
	var buf []byte
	var cc int
	for cc, _ = os.Stdin.Read(buffer[:]); cc == 1; cc, _ = os.Stdin.Read(buffer[:]) {
		if buffer[0] == 'R' {
			break
		}
		buf = append(buf, buffer[0])
	}
	if string(buf[0:2]) != "\x1b[" {
		log.Printf("Failed to read rows;cols from tty\n")
		return -1
	}
	if n, e := fmt.Sscanf(string(buf[2:]), "%d;%d", rows, cols); n != 2 || e != nil {
		if e != nil {
			log.Printf("getCursorPosition: fmt.Sscanf() failed: %s\n", e)
		}
		if n != 2 {
			log.Printf("getCursorPosition: got %d items, wanted 2\n", n)
		}
		return -1
	}
	return 0
}

func getWindowSize(rows *int, cols *int) int {
	var w WinSize
	_, _, err := syscall.Syscall(syscall.SYS_IOCTL,
		os.Stdout.Fd(),
		syscall.TIOCGWINSZ,
		uintptr(unsafe.Pointer(&w)),
	)
	if err != 0 { // type syscall.Errno
		io.WriteString(os.Stdout, "\x1b[999C\x1b[999B")
		return getCursorPosition(rows, cols)
	} else {
		*rows = int(w.Row)
		*cols = int(w.Col)
		return 0
	}
	return -1
}

/*** row operations ***/

func editorAppendRow(s []byte) {
	var r erow
	r.chars = s
	r.size = len(s)
	E.rows = append(E.rows, r)
	E.numRows++
}

/*** file I/O ***/

func editorOpen(filename string) {
	fd, err := os.Open(filename)
	if err != nil {
		die(err)
	}
	defer fd.Close()
	fp := bufio.NewReader(fd)

	for line, err := fp.ReadBytes('\n'); err == nil; line, err = fp.ReadBytes('\n') {
		// Trim trailing newlines and carriage returns
		for c := line[len(line) - 1]; len(line) > 0 && (c == '\n' || c == '\r'); {
			line = line[:len(line)-1]
			if len(line) > 0 {
				c = line[len(line) - 1]
			}
		}
		editorAppendRow(line)
	}

	if err != nil && err != io.EOF {
		die(err)
	}
}

/*** input ***/

func editorMoveCursor(key int) {
	switch key {
	case ARROW_LEFT:
		if E.cx != 0 {
			E.cx--
		} else if E.cy > 0 {
			E.cy--
			E.cx = E.rows[E.cy].size
		}
	case ARROW_RIGHT:
		if E.cy < E.numRows {
			if E.cx < E.rows[E.cy].size {
				E.cx++
			} else if E.cx == E.rows[E.cy].size {
				E.cy++
				E.cx = 0
			}
		}
	case ARROW_UP:
		if E.cy != 0 {
			E.cy--
		}
	case ARROW_DOWN:
		if E.cy < E.numRows {
			E.cy++
		}
	}

	rowlen := 0
	if E.cy < E.numRows {
		rowlen = E.rows[E.cy].size
	}
	if E.cx > rowlen {
		E.cx = rowlen
	}
}

func editorProcessKeypress() {
	c := editorReadKey()
	switch c {
	case ('q' & 0x1f):
		io.WriteString(os.Stdout, "\x1b[2J")
		io.WriteString(os.Stdout, "\x1b[H")
		disableRawMode()
		os.Exit(0)
	case HOME_KEY:
		E.cx = 0
	case END_KEY:
		E.cx = E.screenCols - 1
	case PAGE_UP, PAGE_DOWN:
		dir := ARROW_DOWN
		if c == PAGE_UP {
			dir = ARROW_UP
		}
		for times := E.screenRows; times > 0; times-- {
			editorMoveCursor(dir)
		}
	case ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT:
		editorMoveCursor(c)
	}
}

/*** append buffer ***/

type abuf struct {
	buf []byte
}

func (p abuf) String() string {
	return string(p.buf)
}

func (p *abuf) abAppend(s string) {
	p.buf = append(p.buf, []byte(s)...)
}

func (p *abuf) abAppendBytes(b []byte) {
	p.buf = append(p.buf, b...)
}

/*** output ***/

func editorScroll() {
	if E.cy < E.rowoff {
		E.rowoff = E.cy
	}
	if E.cy >= E.rowoff + E.screenRows {
		E.rowoff = E.cy - E.screenRows + 1
	}
	if E.cx < E.coloff {
		E.coloff = E.cx
	}
	if E.cx >= E.coloff + E.screenCols {
		E.coloff = E.cx - E.screenCols + 1
	}
}

func editorRefreshScreen() {
	editorScroll()
	var ab abuf
	ab.abAppend("\x1b[25l")
	ab.abAppend("\x1b[H")
	editorDrawRows(&ab)
	ab.abAppend(fmt.Sprintf("\x1b[%d;%dH", (E.cy - E.rowoff) + 1, (E.cx - E.coloff) + 1))
	ab.abAppend("\x1b[25h")
	_, e := io.WriteString(os.Stdout, ab.String())
	if e != nil {
		log.Fatal(e)
	}
}

func editorDrawRows(ab *abuf) {
	for y := 0; y < E.screenRows; y++ {
		filerow := y + E.rowoff
		if filerow >= E.numRows {
			if E.numRows == 0 && y == E.screenRows/3 {
				w := fmt.Sprintf("Kilo editor -- version %s", KILO_VERSION)
				if len(w) > E.screenCols {
					w = w[0:E.screenCols]
				}
				pad := "~ "
				for padding := (E.screenCols - len(w)) / 2; padding > 0; padding-- {
					ab.abAppend(pad)
					pad = " "
				}
				ab.abAppend(w)
			} else {
				ab.abAppend("~")
			}
		} else {
			len := E.rows[filerow].size - E.coloff
			if len < 0 { len = 0 }
			if len > E.screenCols { len = E.screenCols }
			ab.abAppendBytes(E.rows[filerow].chars[E.coloff:E.coloff+len])
		}
		ab.abAppend("\x1b[K")
		if y < E.screenRows-1 {
			ab.abAppend("\r\n")
		}
	}
}

/*** init ***/

func initEditor() {
	// Initialization a la C not necessary.
	if getWindowSize(&E.screenRows, &E.screenCols) == -1 {
		die(fmt.Errorf("couldn't get screen size"))
	}
}

func main() {
	enableRawMode()
	defer disableRawMode()
	initEditor()
	if len(os.Args) > 1 {
		editorOpen(os.Args[1])
	}

	for {
		editorRefreshScreen()
		editorProcessKeypress()
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"os"
	"syscall"
	"unsafe"
)

/*** defines ***/

const KILO_VERSION = "0.0.1"
const KILO_TAB_STOP = 8
const (
	ARROW_LEFT  = 1000 + iota
	ARROW_RIGHT = 1000 + iota
	ARROW_UP    = 1000 + iota
	ARROW_DOWN  = 1000 + iota
	DEL_KEY     = 1000 + iota
	HOME_KEY    = 1000 + iota
	END_KEY     = 1000 + iota
	PAGE_UP     = 1000 + iota
	PAGE_DOWN   = 1000 + iota
)

/*** data ***/

type Termios struct {
	Iflag  uint32
	Oflag  uint32
	Cflag  uint32
	Lflag  uint32
	Cc     [20]byte
	Ispeed uint32
	Ospeed uint32
}

type erow struct {
	size   int
	rsize  int
	chars  []byte
	render []byte
}

type editorConfig struct {
	cx          int
	cy          int
	rx          int
	rowoff      int
	coloff      int
	screenRows  int
	screenCols  int
	numRows     int
	rows        []erow
	origTermios *Termios
}

type WinSize struct {
	Row    uint16
	Col    uint16
	Xpixel uint16
	Ypixel uint16
}

var E editorConfig

/*** terminal ***/

func die(err error) {
	disableRawMode()
	io.WriteString(os.Stdout, "\x1b[2J")
	io.WriteString(os.Stdout, "\x1b[H")
	log.Fatal(err)
}

func TcSetAttr(fd uintptr, termios *Termios) error {
	// TCSETS+1 == TCSETSW, because TCSAFLUSH doesn't exist
	if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TCSETS+1), uintptr(unsafe.Pointer(termios))); err != 0 {
		return err
	}
	return nil
}

func TcGetAttr(fd uintptr) *Termios {
	var termios = &Termios{}
	if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, syscall.TCGETS, uintptr(unsafe.Pointer(termios))); err != 0 {
		log.Fatalf("Problem getting terminal attributes: %s\n", err)
	}
	return termios
}

func enableRawMode() {
	E.origTermios = TcGetAttr(os.Stdin.Fd())
	var raw Termios
	raw = *E.origTermios
	raw.Iflag &^= syscall.BRKINT | syscall.ICRNL | syscall.INPCK | syscall.ISTRIP | syscall.IXON
	raw.Oflag &^= syscall.OPOST
	raw.Cflag |= syscall.CS8
	raw.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.IEXTEN | syscall.ISIG
	raw.Cc[syscall.VMIN+1] = 0
	raw.Cc[syscall.VTIME+1] = 1
	if e := TcSetAttr(os.Stdin.Fd(), &raw); e != nil {
		log.Fatalf("Problem enabling raw mode: %s\n", e)
	}
}

func disableRawMode() {
	if e := TcSetAttr(os.Stdin.Fd(), E.origTermios); e != nil {
		log.Fatalf("Problem disabling raw mode: %s\n", e)
	}
}

func editorReadKey() int {
	var buffer [1]byte
	var cc int
	var err error
	for cc, err = os.Stdin.Read(buffer[:]); cc != 1; cc, err = os.Stdin.Read(buffer[:]) {
	}
	if err != nil {
		die(err)
	}
	if buffer[0] == '\x1b' {
		var seq [2]byte
		if cc, _ = os.Stdin.Read(seq[:]); cc != 2 {
			return '\x1b'
		}

		if seq[0] == '[' {
			if seq[1] >= '0' && seq[1] <= '9' {
				if cc, err = os.Stdin.Read(buffer[:]); cc != 1 {
					return '\x1b'
				}
				if buffer[0] == '~' {
					switch seq[1] {
					case '1':
						return HOME_KEY
					case '3':
						return DEL_KEY
					case '4':
						return END_KEY
					case '5':
						return PAGE_UP
					case '6':
						return PAGE_DOWN
					case '7':
						return HOME_KEY
					case '8':
						return END_KEY
					}
				}
				// XXX - what happens here?
			} else {
				switch seq[1] {
				case 'A':
					return ARROW_UP
				case 'B':
					return ARROW_DOWN
				case 'C':
					return ARROW_RIGHT
				case 'D':
					return ARROW_LEFT
				case 'H':
					return HOME_KEY
				case 'F':
					return END_KEY
				}
			}
		} else if seq[0] == '0' {
			switch seq[1] {
			case 'H':
				return HOME_KEY
			case 'F':
				return END_KEY
			}
		}

		return '\x1b'
	}
	return int(buffer[0])
}

func getCursorPosition(rows *int, cols *int) int {
	io.WriteString(os.Stdout, "\x1b[6n")
	var buffer [1]byte
	var buf []byte
	var cc int
	for cc, _ = os.Stdin.Read(buffer[:]); cc == 1; cc, _ = os.Stdin.Read(buffer[:]) {
		if buffer[0] == 'R' {
			break
		}
		buf = append(buf, buffer[0])
	}
	if string(buf[0:2]) != "\x1b[" {
		log.Printf("Failed to read rows;cols from tty\n")
		return -1
	}
	if n, e := fmt.Sscanf(string(buf[2:]), "%d;%d", rows, cols); n != 2 || e != nil {
		if e != nil {
			log.Printf("getCursorPosition: fmt.Sscanf() failed: %s\n", e)
		}
		if n != 2 {
			log.Printf("getCursorPosition: got %d items, wanted 2\n", n)
		}
		return -1
	}
	return 0
}

func getWindowSize(rows *int, cols *int) int {
	var w WinSize
	_, _, err := syscall.Syscall(syscall.SYS_IOCTL,
		os.Stdout.Fd(),
		syscall.TIOCGWINSZ,
		uintptr(unsafe.Pointer(&w)),
	)
	if err != 0 { // type syscall.Errno
		io.WriteString(os.Stdout, "\x1b[999C\x1b[999B")
		return getCursorPosition(rows, cols)
	} else {
		*rows = int(w.Row)
		*cols = int(w.Col)
		return 0
	}
	return -1
}

/*** row operations ***/

func editorRowCxToRx(row *erow, cx int) int {
	rx := 0
	for j := 0; j < row.size && j < cx; j++ {
		if row.chars[j] == '\t' {
			rx += ((KILO_TAB_STOP - 1) - (rx % KILO_TAB_STOP))
		}
		rx++
	}
	return rx
}

func editorUpdateRow(row *erow) {
	tabs := 0
	for _, c := range row.chars {
		if c == '\t' {
			tabs++
		}
	}
	row.render = make([]byte, row.size + tabs*(KILO_TAB_STOP - 1))

	idx := 0
	for _, c := range row.chars {
		if c == '\t' {
			row.render[idx] = ' '
			idx++
			for (idx%KILO_TAB_STOP) != 0 {
				row.render[idx] = ' '
				idx++
			}
		} else {
			row.render[idx] = c
			idx++
		}
	}
	row.rsize = idx
}

func editorAppendRow(s []byte) {
	var r erow
	r.chars = s
	r.size = len(s)
	E.rows = append(E.rows, r)
	editorUpdateRow(&E.rows[E.numRows])
	E.numRows++
}

/*** file I/O ***/

func editorOpen(filename string) {
	fd, err := os.Open(filename)
	if err != nil {
		die(err)
	}
	defer fd.Close()
	fp := bufio.NewReader(fd)

	for line, err := fp.ReadBytes('\n'); err == nil; line, err = fp.ReadBytes('\n') {
		// Trim trailing newlines and carriage returns
		for c := line[len(line) - 1]; len(line) > 0 && (c == '\n' || c == '\r'); {
			line = line[:len(line)-1]
			if len(line) > 0 {
				c = line[len(line) - 1]
			}
		}
		editorAppendRow(line)
	}

	if err != nil && err != io.EOF {
		die(err)
	}
}

/*** input ***/

func editorMoveCursor(key int) {
	switch key {
	case ARROW_LEFT:
		if E.cx != 0 {
			E.cx--
		} else if E.cy > 0 {
			E.cy--
			E.cx = E.rows[E.cy].rsize
		}
	case ARROW_RIGHT:
		if E.cy < E.numRows {
			if E.cx < E.rows[E.cy].rsize {
				E.cx++
			} else if E.cx == E.rows[E.cy].rsize {
				E.cy++
				E.cx = 0
			}
		}
	case ARROW_UP:
		if E.cy != 0 {
			E.cy--
		}
	case ARROW_DOWN:
		if E.cy < E.numRows {
			E.cy++
		}
	}

	rowlen := 0
	if E.cy < E.numRows {
		rowlen = E.rows[E.cy].rsize
	}
	if E.cx > rowlen {
		E.cx = rowlen
	}
}

func editorProcessKeypress() {
	c := editorReadKey()
	switch c {
	case ('q' & 0x1f):
		io.WriteString(os.Stdout, "\x1b[2J")
		io.WriteString(os.Stdout, "\x1b[H")
		disableRawMode()
		os.Exit(0)
	case HOME_KEY:
		E.cx = 0
	case END_KEY:
		E.cx = E.screenCols - 1
	case PAGE_UP, PAGE_DOWN:
		dir := ARROW_DOWN
		if c == PAGE_UP {
			dir = ARROW_UP
		}
		for times := E.screenRows; times > 0; times-- {
			editorMoveCursor(dir)
		}
	case ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT:
		editorMoveCursor(c)
	}
}

/*** append buffer ***/

type abuf struct {
	buf []byte
}

func (p abuf) String() string {
	return string(p.buf)
}

func (p *abuf) abAppend(s string) {
	p.buf = append(p.buf, []byte(s)...)
}

func (p *abuf) abAppendBytes(b []byte) {
	p.buf = append(p.buf, b...)
}

/*** output ***/

func editorScroll() {
	E.rx = 0

	if (E.cy < E.numRows) {
		E.rx = editorRowCxToRx(&(E.rows[E.cy]), E.cx)
	}

	if E.cy < E.rowoff {
		E.rowoff = E.cy
	}
	if E.cy >= E.rowoff + E.screenRows {
		E.rowoff = E.cy - E.screenRows + 1
	}
	if E.rx < E.coloff {
		E.coloff = E.rx
	}
	if E.rx >= E.coloff + E.screenCols {
		E.coloff = E.rx - E.screenCols + 1
	}
}

func editorRefreshScreen() {
	editorScroll()
	var ab abuf
	ab.abAppend("\x1b[25l")
	ab.abAppend("\x1b[H")
	editorDrawRows(&ab)
	ab.abAppend(fmt.Sprintf("\x1b[%d;%dH", (E.cy - E.rowoff) + 1, (E.rx - E.coloff) + 1))
	ab.abAppend("\x1b[?25h")
	_, e := io.WriteString(os.Stdout, ab.String())
	if e != nil {
		log.Fatal(e)
	}
}

func editorDrawRows(ab *abuf) {
	for y := 0; y < E.screenRows; y++ {
		filerow := y + E.rowoff
		if filerow >= E.numRows {
			if E.numRows == 0 && y == E.screenRows/3 {
				w := fmt.Sprintf("Kilo editor -- version %s", KILO_VERSION)
				if len(w) > E.screenCols {
					w = w[0:E.screenCols]
				}
				pad := "~ "
				for padding := (E.screenCols - len(w)) / 2; padding > 0; padding-- {
					ab.abAppend(pad)
					pad = " "
				}
				ab.abAppend(w)
			} else {
				ab.abAppend("~")
			}
		} else {
			len := E.rows[filerow].rsize - E.coloff
			if len < 0 { len = 0 }
			if len > E.screenCols { len = E.screenCols }
			ab.abAppendBytes(E.rows[filerow].render[E.coloff:E.coloff+len])
		}
		ab.abAppend("\x1b[K")
		if y < E.screenRows-1 {
			ab.abAppend("\r\n")
		}
	}
}

/*** init ***/

func initEditor() {
	// Initialization a la C not necessary.
	if getWindowSize(&E.screenRows, &E.screenCols) == -1 {
		die(fmt.Errorf("couldn't get screen size"))
	}
}

func main() {
	enableRawMode()
	defer disableRawMode()
	initEditor()
	if len(os.Args) > 1 {
		editorOpen(os.Args[1])
	}

	for {
		editorRefreshScreen()
		editorProcessKeypress()
	}
}
<commit_msg>Step 90, but there's moving off LHS of a row problems.<commit_after>package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"os"
	"syscall"
	"unsafe"
)

/*** defines ***/

const KILO_VERSION = "0.0.1"
const KILO_TAB_STOP = 8
const (
	ARROW_LEFT  = 1000 + iota
	ARROW_RIGHT = 1000 + iota
	ARROW_UP    = 1000 + iota
	ARROW_DOWN  = 1000 + iota
	DEL_KEY     = 1000 + iota
	HOME_KEY    = 1000 + iota
	END_KEY     = 1000 + iota
	PAGE_UP     = 1000 + iota
	PAGE_DOWN   = 1000 + iota
)

/*** data ***/

type Termios struct {
	Iflag  uint32
	Oflag  uint32
	Cflag  uint32
	Lflag  uint32
	Cc     [20]byte
	Ispeed uint32
	Ospeed uint32
}

type erow struct {
	size   int
	rsize  int
	chars  []byte
	render []byte
}

type editorConfig struct {
	cx          int
	cy          int
	rx          int
	rowoff      int
	coloff      int
	screenRows  int
	screenCols  int
	numRows     int
	rows        []erow
	origTermios *Termios
}

type WinSize struct {
	Row    uint16
	Col    uint16
	Xpixel uint16
	Ypixel uint16
}

var E editorConfig

/*** terminal ***/

func die(err error) {
	disableRawMode()
	io.WriteString(os.Stdout, "\x1b[2J")
	io.WriteString(os.Stdout, "\x1b[H")
	log.Fatal(err)
}

func TcSetAttr(fd uintptr, termios *Termios) error {
	// TCSETS+1 == TCSETSW, because TCSAFLUSH doesn't exist
	if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TCSETS+1), uintptr(unsafe.Pointer(termios))); err != 0 {
		return err
	}
	return nil
}

func TcGetAttr(fd uintptr) *Termios {
	var termios = &Termios{}
	if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, syscall.TCGETS, uintptr(unsafe.Pointer(termios))); err != 0 {
		log.Fatalf("Problem getting terminal attributes: %s\n", err)
	}
	return termios
}

func enableRawMode() {
	E.origTermios = TcGetAttr(os.Stdin.Fd())
	var raw Termios
	raw = *E.origTermios
	raw.Iflag &^= syscall.BRKINT | syscall.ICRNL | syscall.INPCK | syscall.ISTRIP | syscall.IXON
	raw.Oflag &^= syscall.OPOST
	raw.Cflag |= syscall.CS8
	raw.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.IEXTEN | syscall.ISIG
	raw.Cc[syscall.VMIN+1] = 0
	raw.Cc[syscall.VTIME+1] = 1
	if e := TcSetAttr(os.Stdin.Fd(), &raw); e != nil {
		log.Fatalf("Problem enabling raw mode: %s\n", e)
	}
}

func disableRawMode() {
	if e := TcSetAttr(os.Stdin.Fd(), E.origTermios); e != nil {
		log.Fatalf("Problem disabling raw mode: %s\n", e)
	}
}

func editorReadKey() int {
	var buffer [1]byte
	var cc int
	var err error
	for cc, err = os.Stdin.Read(buffer[:]); cc != 1; cc, err = os.Stdin.Read(buffer[:]) {
	}
	if err != nil {
		die(err)
	}
	if buffer[0] == '\x1b' {
		var seq [2]byte
		if cc, _ = os.Stdin.Read(seq[:]); cc != 2 {
			return '\x1b'
		}

		if seq[0] == '[' {
			if seq[1] >= '0' && seq[1] <= '9' {
				if cc, err = os.Stdin.Read(buffer[:]); cc != 1 {
					return '\x1b'
				}
				if buffer[0] == '~' {
					switch seq[1] {
					case '1':
						return HOME_KEY
					case '3':
						return DEL_KEY
					case '4':
						return END_KEY
					case '5':
						return PAGE_UP
					case '6':
						return PAGE_DOWN
					case '7':
						return HOME_KEY
					case '8':
						return END_KEY
					}
				}
				// XXX - what happens here?
			} else {
				switch seq[1] {
				case 'A':
					return ARROW_UP
				case 'B':
					return ARROW_DOWN
				case 'C':
					return ARROW_RIGHT
				case 'D':
					return ARROW_LEFT
				case 'H':
					return HOME_KEY
				case 'F':
					return END_KEY
				}
			}
		} else if seq[0] == '0' {
			switch seq[1] {
			case 'H':
				return HOME_KEY
			case 'F':
				return END_KEY
			}
		}

		return '\x1b'
	}
	return int(buffer[0])
}

func getCursorPosition(rows *int, cols *int) int {
	io.WriteString(os.Stdout, "\x1b[6n")
	var buffer [1]byte
	var buf []byte
	var cc int
	for cc, _ = os.Stdin.Read(buffer[:]); cc == 1; cc, _ = os.Stdin.Read(buffer[:]) {
		if buffer[0] == 'R' {
			break
		}
		buf = append(buf, buffer[0])
	}
	if string(buf[0:2]) != "\x1b[" {
		log.Printf("Failed to read rows;cols from tty\n")
		return -1
	}
	if n, e := fmt.Sscanf(string(buf[2:]), "%d;%d", rows, cols); n != 2 || e != nil {
		if e != nil {
			log.Printf("getCursorPosition: fmt.Sscanf() failed: %s\n", e)
		}
		if n != 2 {
			log.Printf("getCursorPosition: got %d items, wanted 2\n", n)
		}
		return -1
	}
	return 0
}

func getWindowSize(rows *int, cols *int) int {
	var w WinSize
	_, _, err := syscall.Syscall(syscall.SYS_IOCTL,
		os.Stdout.Fd(),
		syscall.TIOCGWINSZ,
		uintptr(unsafe.Pointer(&w)),
	)
	if err != 0 { // type syscall.Errno
		io.WriteString(os.Stdout, "\x1b[999C\x1b[999B")
		return getCursorPosition(rows, cols)
	} else {
		*rows = int(w.Row)
		*cols = int(w.Col)
		return 0
	}
	return -1
}

/*** row operations ***/

func editorRowCxToRx(row *erow, cx int) int {
	rx := 0
	for j := 0; j < row.size && j < cx; j++ {
		if row.chars[j] == '\t' {
			rx += ((KILO_TAB_STOP - 1) - (rx % KILO_TAB_STOP))
		}
		rx++
	}
	return rx
}

func editorUpdateRow(row *erow) {
	tabs := 0
	for _, c := range row.chars {
		if c == '\t' {
			tabs++
		}
	}
	row.render = make([]byte, row.size + tabs*(KILO_TAB_STOP - 1))

	idx := 0
	for _, c := range row.chars {
		if c == '\t' {
			row.render[idx] = ' '
			idx++
			for (idx%KILO_TAB_STOP) != 0 {
				row.render[idx] = ' '
				idx++
			}
		} else {
			row.render[idx] = c
			idx++
		}
	}
	row.rsize = idx
}

func editorAppendRow(s []byte) {
	var r erow
	r.chars = s
	r.size = len(s)
	E.rows = append(E.rows, r)
	editorUpdateRow(&E.rows[E.numRows])
	E.numRows++
}

/*** file I/O ***/

func editorOpen(filename string) {
	fd, err := os.Open(filename)
	if err != nil {
		die(err)
	}
	defer fd.Close()
	fp := bufio.NewReader(fd)

	for line, err := fp.ReadBytes('\n'); err == nil; line, err = fp.ReadBytes('\n') {
		// Trim trailing newlines and carriage returns
		for c := line[len(line) - 1]; len(line) > 0 && (c == '\n' || c == '\r'); {
			line = line[:len(line)-1]
			if len(line) > 0 {
				c = line[len(line) - 1]
			}
		}
		editorAppendRow(line)
	}

	if err != nil && err != io.EOF {
		die(err)
	}
}

/*** input ***/

func editorMoveCursor(key int) {
	switch key {
	case ARROW_LEFT:
		if E.cx != 0 {
			E.cx--
		} else if E.cy > 0 {
			E.cy--
			E.cx = E.rows[E.cy].rsize
		}
	case ARROW_RIGHT:
		if E.cy < E.numRows {
			if E.cx < E.rows[E.cy].rsize {
				E.cx++
			} else if E.cx == E.rows[E.cy].rsize {
				E.cy++
				E.cx = 0
			}
		}
	case ARROW_UP:
		if E.cy != 0 {
			E.cy--
		}
	case ARROW_DOWN:
		if E.cy < E.numRows {
			E.cy++
		}
	}

	rowlen := 0
	if E.cy < E.numRows {
		rowlen = E.rows[E.cy].rsize
	}
	if E.cx > rowlen {
		E.cx = rowlen
	}
}

func editorProcessKeypress() {
	c := editorReadKey()
	switch c {
	case ('q' & 0x1f):
		io.WriteString(os.Stdout, "\x1b[2J")
		io.WriteString(os.Stdout, "\x1b[H")
		disableRawMode()
		os.Exit(0)
	case HOME_KEY:
		E.cx = 0
	case END_KEY:
		E.cx = E.screenCols - 1
	case PAGE_UP, PAGE_DOWN:
		dir := ARROW_DOWN
		if c == PAGE_UP {
			E.cy = E.rowoff
			dir = ARROW_UP
		} else {
			E.cy = E.rowoff + E.screenRows - 1
			if E.cy > E.numRows { E.cy = E.numRows }
		}
		for times := E.screenRows; times > 0; times-- {
			editorMoveCursor(dir)
		}
	case ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT:
		editorMoveCursor(c)
	}
}

/*** append buffer ***/

type abuf struct {
	buf []byte
}

func (p abuf) String() string {
	return string(p.buf)
}

func (p *abuf) abAppend(s string) {
	p.buf = append(p.buf, []byte(s)...)
}

func (p *abuf) abAppendBytes(b []byte) {
	p.buf = append(p.buf, b...)
}

/*** output ***/

func editorScroll() {
	E.rx = 0

	if (E.cy < E.numRows) {
		E.rx = editorRowCxToRx(&(E.rows[E.cy]), E.cx)
	}

	if E.cy < E.rowoff {
		E.rowoff = E.cy
	}
	if E.cy >= E.rowoff + E.screenRows {
		E.rowoff = E.cy - E.screenRows + 1
	}
	if E.rx < E.coloff {
		E.coloff = E.rx
	}
	if E.rx >= E.coloff + E.screenCols {
		E.coloff = E.rx - E.screenCols + 1
	}
}

func editorRefreshScreen() {
	editorScroll()
	var ab abuf
	ab.abAppend("\x1b[25l")
	ab.abAppend("\x1b[H")
	editorDrawRows(&ab)
	ab.abAppend(fmt.Sprintf("\x1b[%d;%dH", (E.cy - E.rowoff) + 1, (E.rx - E.coloff) + 1))
	ab.abAppend("\x1b[?25h")
	_, e := io.WriteString(os.Stdout, ab.String())
	if e != nil {
		log.Fatal(e)
	}
}

func editorDrawRows(ab *abuf) {
	for y := 0; y < E.screenRows; y++ {
		filerow := y + E.rowoff
		if filerow >= E.numRows {
			if E.numRows == 0 && y == E.screenRows/3 {
				w := fmt.Sprintf("Kilo editor -- version %s", KILO_VERSION)
				if len(w) > E.screenCols {
					w = w[0:E.screenCols]
				}
				pad := "~ "
				for padding := (E.screenCols - len(w)) / 2; padding > 0; padding-- {
					ab.abAppend(pad)
					pad = " "
				}
				ab.abAppend(w)
			} else {
				ab.abAppend("~")
			}
		} else {
			len := E.rows[filerow].rsize - E.coloff
			if len < 0 { len = 0 }
			if len > E.screenCols { len = E.screenCols }
			ab.abAppendBytes(E.rows[filerow].render[E.coloff:E.coloff+len])
		}
		ab.abAppend("\x1b[K")
		if y < E.screenRows-1 {
			ab.abAppend("\r\n")
		}
	}
}

/*** init ***/

func initEditor() {
	// Initialization a la C not necessary.
	if getWindowSize(&E.screenRows, &E.screenCols) == -1 {
		die(fmt.Errorf("couldn't get screen size"))
	}
}

func main() {
	enableRawMode()
	defer disableRawMode()
	initEditor()
	if len(os.Args) > 1 {
		editorOpen(os.Args[1])
	}

	for {
		editorRefreshScreen()
		editorProcessKeypress()
	}
}
<|endoftext|>


<commit_before>package kkok

import (
	"regexp"
	"sync"

	"github.com/cybozu-go/log"
	"github.com/pkg/errors"
)

var (
	reRouteID = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)
)

// Kkok is the struct to compose kkok.
//
// Internal APIs to work on generators/routes/filters are provided by this.
type Kkok struct {
	// lock for routes
	lkr sync.Mutex

	// routes maps route ID to a route (= list of transports).
	routes map[string][]Transport

	// lock for filters
	lkf sync.Mutex

	// filters are ordered as defined.
	filters []Filter
}

// NewKkok constructs a new empty Kkok.
func NewKkok() *Kkok {
	return &Kkok{
		routes:  make(map[string][]Transport),
		filters: make([]Filter, 0, 10),
	}
}

// RouteIDs return a slice of route IDs.
func (k *Kkok) RouteIDs() []string {
	k.lkr.Lock()
	defer k.lkr.Unlock()

	ids := make([]string, 0, len(k.routes))
	for id := range k.routes {
		ids = append(ids, id)
	}
	return ids
}

// Filters return a snapshot of the current filters.
func (k *Kkok) Filters() []Filter {
	k.lkf.Lock()
	defer k.lkf.Unlock()

	k.gc()
	filters := make([]Filter, len(k.filters))
	for i, f := range k.filters {
		filters[i] = f
	}
	return filters
}

// Handle implements AlertHandler interface.
func (k *Kkok) Handle(alerts []*Alert) {
	if len(alerts) == 0 {
		return
	}

	var err error
	for _, f := range k.Filters() {
		if f.Disabled() {
			continue
		}

		err = f.Reload()
		if err != nil {
			log.Error("[kkok] failed to reload", map[string]interface{}{
				log.FnError: err.Error(),
				"filter":    f.ID(),
			})
			return
		}

		alerts, err = f.Process(alerts)
		if err != nil {
			log.Error("[kkok] failed to filter alerts", map[string]interface{}{
				log.FnError: err.Error(),
				"filter":    f.ID(),
				"nalerts":   len(alerts),
			})
			return
		}

		if len(alerts) == 0 {
			log.Info("[kkok] filters reduced all alerts", map[string]interface{}{
				"filter": f.ID(),
			})
			return
		}
	}

	k.sendAlerts(alerts)
}

// AddRoute adds or replaces a route with id.
func (k *Kkok) AddRoute(id string, route []Transport) error {
	if !reRouteID.MatchString(id) {
		return errors.New("invalid route id: " + id)
	}

	k.lkr.Lock()
	k.routes[id] = route
	k.lkr.Unlock()
	return nil
}

// AddStaticFilter adds a filter statically.
func (k *Kkok) AddStaticFilter(filter Filter) error {
	k.lkf.Lock()
	defer k.lkf.Unlock()

	k.gc()

	id := filter.ID()

	for _, f := range k.filters {
		if f.ID() == id {
			return errors.New("duplicate filter id: " + id)
		}
	}

	k.filters = append(k.filters, filter)
	return nil
}

// PutFilter adds or replaces a filter with filter.ID().
func (k *Kkok) PutFilter(filter Filter) {
	k.lkf.Lock()
	defer k.lkf.Unlock()

	k.gc()

	id := filter.ID()

	for i, f := range k.filters {
		if f.ID() != id {
			continue
		}
		if f.Dynamic() {
			filter.SetDynamic()
		}
		k.filters[i] = filter
		return
	}

	filter.SetDynamic()
	k.filters = append(k.filters, filter)
}

// Internal APIs

func (k *Kkok) getRoute(id string) []Transport {
	k.lkr.Lock()
	defer k.lkr.Unlock()

	return k.routes[id]
}

func (k *Kkok) gc() {
	n := 0
	for _, f := range k.filters {
		if !f.Dynamic() || !f.Expired() {
			k.filters[n] = f
			n++
		}
	}
	k.filters = k.filters[0:n]
}

func (k *Kkok) removeFilter(id string) error {
	k.lkf.Lock()
	defer k.lkf.Unlock()

	n := 0
	for _, f := range k.filters {
		if f.ID() != id {
			if !f.Dynamic() || !f.Expired() {
				k.filters[n] = f
				n++
			}
			continue
		}

		if !f.Dynamic() {
			return errors.New("static filters cannot be removed")
		}
	}

	k.filters = k.filters[0:n]
	return nil
}

func (k *Kkok) getFilter(id string) Filter {
	k.lkf.Lock()
	defer k.lkf.Unlock()

	for _, f := range k.filters {
		if f.ID() == id {
			return f
		}
	}
	return nil
}

func (k *Kkok) sendAlerts(alerts []*Alert) {
	k.lkr.Lock()
	defer k.lkr.Unlock()

	routedAlerts := make(map[string][]*Alert)
	for id := range k.routes {
		routedAlerts[id] = make([]*Alert, 0)
	}

	for _, a := range alerts {
		for _, id := range a.Routes {
			_, ok := routedAlerts[id]
			if !ok {
				log.Warn("[kkok] unknown route", map[string]interface{}{
					"route": id,
					"title": a.Title,
				})
				continue
			}
			routedAlerts[id] = append(routedAlerts[id], a)
		}
	}

	for id, r := range k.routes {
		alerts = routedAlerts[id]
		if len(alerts) == 0 {
			continue
		}

		log.Info("[kkok] sending alerts", map[string]interface{}{
			"route":   id,
			"nalerts": len(alerts),
		})

		for _, t := range r {
			err := t.Deliver(alerts)
			if err != nil {
				log.Error("[kkok] failed to send alerts", map[string]interface{}{
					log.FnError: err.Error(),
					"route":     id,
					"transport": t.String(),
				})
			}
		}
	}
}
<commit_msg>minimize lock duration<commit_after>package kkok

import (
	"regexp"
	"sync"

	"github.com/cybozu-go/log"
	"github.com/pkg/errors"
)

var (
	reRouteID = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)
)

// Kkok is the struct to compose kkok.
//
// Internal APIs to work on generators/routes/filters are provided by this.
type Kkok struct {
	// lock for routes
	lkr sync.Mutex

	// routes maps route ID to a route (= list of transports).
	routes map[string][]Transport

	// lock for filters
	lkf sync.Mutex

	// filters are ordered as defined.
	filters []Filter
}

// NewKkok constructs a new empty Kkok.
func NewKkok() *Kkok {
	return &Kkok{
		routes:  make(map[string][]Transport),
		filters: make([]Filter, 0, 10),
	}
}

// RouteIDs return a slice of route IDs.
func (k *Kkok) RouteIDs() []string {
	k.lkr.Lock()
	defer k.lkr.Unlock()

	ids := make([]string, 0, len(k.routes))
	for id := range k.routes {
		ids = append(ids, id)
	}
	return ids
}

// Filters return a snapshot of the current filters.
func (k *Kkok) Filters() []Filter {
	k.lkf.Lock()
	defer k.lkf.Unlock()

	k.gc()
	filters := make([]Filter, len(k.filters))
	for i, f := range k.filters {
		filters[i] = f
	}
	return filters
}

// Handle implements AlertHandler interface.
func (k *Kkok) Handle(alerts []*Alert) {
	if len(alerts) == 0 {
		return
	}

	var err error
	for _, f := range k.Filters() {
		if f.Disabled() {
			continue
		}

		err = f.Reload()
		if err != nil {
			log.Error("[kkok] failed to reload", map[string]interface{}{
				log.FnError: err.Error(),
				"filter":    f.ID(),
			})
			return
		}

		alerts, err = f.Process(alerts)
		if err != nil {
			log.Error("[kkok] failed to filter alerts", map[string]interface{}{
				log.FnError: err.Error(),
				"filter":    f.ID(),
				"nalerts":   len(alerts),
			})
			return
		}

		if len(alerts) == 0 {
			log.Info("[kkok] filters reduced all alerts", map[string]interface{}{
				"filter": f.ID(),
			})
			return
		}
	}

	k.sendAlerts(alerts)
}

// AddRoute adds or replaces a route with id.
func (k *Kkok) AddRoute(id string, route []Transport) error {
	if !reRouteID.MatchString(id) {
		return errors.New("invalid route id: " + id)
	}

	k.lkr.Lock()
	k.routes[id] = route
	k.lkr.Unlock()
	return nil
}

// AddStaticFilter adds a filter statically.
func (k *Kkok) AddStaticFilter(filter Filter) error {
	k.lkf.Lock()
	defer k.lkf.Unlock()

	k.gc()

	id := filter.ID()

	for _, f := range k.filters {
		if f.ID() == id {
			return errors.New("duplicate filter id: " + id)
		}
	}

	k.filters = append(k.filters, filter)
	return nil
}

// PutFilter adds or replaces a filter with filter.ID().
func (k *Kkok) PutFilter(filter Filter) {
	k.lkf.Lock()
	defer k.lkf.Unlock()

	k.gc()

	id := filter.ID()

	for i, f := range k.filters {
		if f.ID() != id {
			continue
		}
		if f.Dynamic() {
			filter.SetDynamic()
		}
		k.filters[i] = filter
		return
	}

	filter.SetDynamic()
	k.filters = append(k.filters, filter)
}

// Internal APIs

func (k *Kkok) getRoute(id string) []Transport {
	k.lkr.Lock()
	defer k.lkr.Unlock()

	return k.routes[id]
}

func (k *Kkok) gc() {
	n := 0
	for _, f := range k.filters {
		if !f.Dynamic() || !f.Expired() {
			k.filters[n] = f
			n++
		}
	}
	k.filters = k.filters[0:n]
}

func (k *Kkok) removeFilter(id string) error {
	k.lkf.Lock()
	defer k.lkf.Unlock()

	n := 0
	for _, f := range k.filters {
		if f.ID() != id {
			if !f.Dynamic() || !f.Expired() {
				k.filters[n] = f
				n++
			}
			continue
		}

		if !f.Dynamic() {
			return errors.New("static filters cannot be removed")
		}
	}

	k.filters = k.filters[0:n]
	return nil
}

func (k *Kkok) getFilter(id string) Filter {
	k.lkf.Lock()
	defer k.lkf.Unlock()

	for _, f := range k.filters {
		if f.ID() == id {
			return f
		}
	}
	return nil
}

func (k *Kkok) sendAlerts(alerts []*Alert) {
	routedAlerts := make(map[string][]*Alert)
	for _, a := range alerts {
		for _, id := range a.Routes {
			routedAlerts[id] = append(routedAlerts[id], a)
		}
	}

	k.lkr.Lock()
	defer k.lkr.Unlock()

	for id, r := range k.routes {
		alerts = routedAlerts[id]
		if len(alerts) == 0 {
			continue
		}

		log.Info("[kkok] sending alerts", map[string]interface{}{
			"route":   id,
			"nalerts": len(alerts),
		})

		for _, t := range r {
			// t.Deliver must not take a long time.
			err := t.Deliver(alerts)
			if err != nil {
				log.Error("[kkok] failed to send alerts", map[string]interface{}{
					log.FnError: err.Error(),
					"route":     id,
					"transport": t.String(),
				})
			}
		}
	}
}
<|endoftext|>