<commit_before>package gitfile

import (
	b64 "encoding/base64"
	"os/exec"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"github.com/hashicorp/terraform/helper/schema"
	"strings"
	"github.com/hashicorp/terraform/terraform"
)

func Provider() terraform.ResourceProvider {
	return &schema.Provider{
		Schema: map[string]*schema.Schema{
			"workdir": &schema.Schema{
				Type: schema.TypeString,
				Required: true,
			},
		},
		ResourcesMap: map[string]*schema.Resource{
			"gitfile_file": fileResource(),
		},
		ConfigureFunc: gitfileConfigure,
	}
}

func gitfileConfigure(data *schema.ResourceData) (interface{}, error) {
	config := &gitfileConfig {
		workDir: data.Get("workdir").(string),
	}
	return config, nil
}

type gitfileConfig struct {
	workDir string
}

func fileResource() *schema.Resource {
	return &schema.Resource {
		Schema: map[string]*schema.Schema {
			"repo": &schema.Schema{
				Type: schema.TypeString,
				Required: true,
			},
			"path": &schema.Schema{
				Type: schema.TypeString,
				Required: true,
			},
			"contents": &schema.Schema{
				Type: schema.TypeString,
				Required: true,
			},
			"branch": &schema.Schema{
				Type: schema.TypeString,
				Optional: true,
				Default: "master",
			},
			"commit_message": &schema.Schema{
				Type: schema.TypeString,
				Optional: true,
				Default: "Created by terraform gitfile_file",
			},
		},
		Create: FileCreate,
		Read: FileRead,
		Update: FileUpdate,
		Delete: FileDelete,
	}
}

func FileCreate(d *schema.ResourceData, meta interface{}) error {
	repo := d.Get("repo").(string)
	branch := d.Get("branch").(string)
	filepath := d.Get("path").(string)
	workdir := meta.(*gitfileConfig).workDir

	d.SetId(fmt.Sprintf("%s %s %s", repo, branch, filepath))

	checkout_dir := path.Join(workdir, mungeGitDir(d.Id()))
	if err := shallowSparseGitCheckout(checkout_dir, repo, branch, filepath); err != nil {
		return err
	}

	return FileUpdate(d, meta)
}
func FileRead(d *schema.ResourceData, meta interface{}) error {
	splits := strings.SplitN(d.Id(), " ", 3)
	repo := splits[0]
	branch := splits[1]
	filepath := splits[2]
	workdir := meta.(*gitfileConfig).workDir

	d.Set("repo", repo)
	d.Set("branch", branch)
	d.Set("path", filepath)

	checkout_dir := path.Join(workdir, mungeGitDir(d.Id()))
	if err := shallowSparseGitCheckout(checkout_dir, repo, branch, filepath); err != nil {
		return err
	}

	contents, err := ioutil.ReadFile(path.Join(checkout_dir, filepath))
	if err != nil {
		if os.IsNotExist(err) {
			d.Set("contents", "")
		} else {
			return err
		}
	} else {
		d.Set("contents", string(contents))
	}
	return nil
}
func FileUpdate(d *schema.ResourceData, meta interface{}) error {
	repo := d.Get("repo").(string)
	branch := d.Get("branch").(string)
	filepath := d.Get("path").(string)
	contents := d.Get("contents").(string)
	commit_message := d.Get("commit_message").(string)
	workdir := meta.(*gitfileConfig).workDir
	checkout_dir := path.Join(workdir, mungeGitDir(d.Id()))

	if err := ioutil.WriteFile(path.Join(checkout_dir, filepath), []byte(contents), 0666); err != nil {
		return err
	}

	git_add := exec.Command("git", "add", "--intent-to-add", "--", filepath)
	git_add.Dir = checkout_dir
	if err := git_add.Run(); err != nil {
		return err
	}

	git_commit := exec.Command("git", "commit", "-m", commit_message, "--", filepath)
	git_commit.Dir = checkout_dir
	if err := git_commit.Run(); err != nil {
		return err
	}

	git_push := exec.Command("git", "push", repo, fmt.Sprintf("HEAD:%s", branch))
	git_push.Dir = checkout_dir
	if err := git_push.Run(); err != nil {
		return err
	}
	return nil
}
func FileDelete(d *schema.ResourceData, meta interface{}) error {
	return nil
}

func mungeGitDir(id string) string {
	return b64.URLEncoding.EncodeToString([]byte(id))
}

func shallowSparseGitCheckout(checkout_dir, repo, branch, filepath string) error {
	if err := os.MkdirAll(checkout_dir, 0755); err != nil {
		return err
	}

	// git init appears to be idempotent.
	git_init := exec.Command("git", "init")
	git_init.Dir = checkout_dir
	if err := git_init.Run(); err != nil {
		return err
	}

	git_config := exec.Command("git", "config", "core.sparsecheckout", "true")
	git_config.Dir = checkout_dir
	if err := git_config.Run(); err != nil {
		return err
	}

	if err := ioutil.WriteFile(
		path.Join(checkout_dir, ".git", "info", "sparse-checkout"),
		[]byte(filepath),
		0666,
	); err != nil {
		return err
	}

	git_fetch := exec.Command("git", "fetch", "--depth", "1", repo, branch)
	git_fetch.Dir = checkout_dir
	if err := git_fetch.Run(); err != nil {
		return err
	}

	git_checkout := exec.Command("git", "checkout", "--force", "FETCH_HEAD")
	git_checkout.Dir = checkout_dir
	if err := git_checkout.Run(); err != nil {
		return err
	}

	git_clean := exec.Command("git", "clean", "-ffdx")
	git_clean.Dir = checkout_dir
	if err := git_clean.Run(); err != nil {
		return err
	}

	return nil
}<commit_msg>fix issue where changing just the commit_message could cause a failed apply.<commit_after>package gitfile

import (
	b64 "encoding/base64"
	"os/exec"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"github.com/hashicorp/terraform/helper/schema"
	"strings"
	"github.com/hashicorp/terraform/terraform"
)

func Provider() terraform.ResourceProvider {
	return &schema.Provider{
		Schema: map[string]*schema.Schema{
			"workdir": &schema.Schema{
				Type: schema.TypeString,
				Required: true,
			},
		},
		ResourcesMap: map[string]*schema.Resource{
			"gitfile_file": fileResource(),
		},
		ConfigureFunc: gitfileConfigure,
	}
}

func gitfileConfigure(data *schema.ResourceData) (interface{}, error) {
	config := &gitfileConfig {
		workDir: data.Get("workdir").(string),
	}
	return config, nil
}

type gitfileConfig struct {
	workDir string
}

func fileResource() *schema.Resource {
	return &schema.Resource {
		Schema: map[string]*schema.Schema {
			"repo": &schema.Schema{
				Type: schema.TypeString,
				Required: true,
			},
			"path": &schema.Schema{
				Type: schema.TypeString,
				Required: true,
			},
			"contents": &schema.Schema{
				Type: schema.TypeString,
				Required: true,
			},
			"branch": &schema.Schema{
				Type: schema.TypeString,
				Optional: true,
				Default: "master",
			},
			"commit_message": &schema.Schema{
				Type: schema.TypeString,
				Optional: true,
				Default: "Created by terraform gitfile_file",
			},
		},
		Create: FileCreate,
		Read: FileRead,
		Update: FileUpdate,
		Delete: FileDelete,
	}
}

func FileCreate(d *schema.ResourceData, meta interface{}) error {
	repo := d.Get("repo").(string)
	branch := d.Get("branch").(string)
	filepath := d.Get("path").(string)
	workdir := meta.(*gitfileConfig).workDir

	d.SetId(fmt.Sprintf("%s %s %s", repo, branch, filepath))

	checkout_dir := path.Join(workdir, mungeGitDir(d.Id()))
	if err := shallowSparseGitCheckout(checkout_dir, repo, branch, filepath); err != nil {
		return err
	}

	return FileUpdate(d, meta)
}
func FileRead(d *schema.ResourceData, meta interface{}) error {
	splits := strings.SplitN(d.Id(), " ", 3)
	repo := splits[0]
	branch := splits[1]
	filepath := splits[2]
	workdir := meta.(*gitfileConfig).workDir

	d.Set("repo", repo)
	d.Set("branch", branch)
	d.Set("path", filepath)

	checkout_dir := path.Join(workdir, mungeGitDir(d.Id()))
	if err := shallowSparseGitCheckout(checkout_dir, repo, branch, filepath); err != nil {
		return err
	}

	contents, err := ioutil.ReadFile(path.Join(checkout_dir, filepath))
	if err != nil {
		if os.IsNotExist(err) {
			d.Set("contents", "")
		} else {
			return err
		}
	} else {
		d.Set("contents", string(contents))
	}
	return nil
}
func FileUpdate(d *schema.ResourceData, meta interface{}) error {
	repo := d.Get("repo").(string)
	branch := d.Get("branch").(string)
	filepath := d.Get("path").(string)
	contents := d.Get("contents").(string)
	commit_message := d.Get("commit_message").(string)
	workdir := meta.(*gitfileConfig).workDir
	checkout_dir := path.Join(workdir, mungeGitDir(d.Id()))

	if err := ioutil.WriteFile(path.Join(checkout_dir, filepath), []byte(contents), 0666); err != nil {
		return err
	}

	// Only bother trying to commit things if the contents have changed.
	// I'm pretty sure this should be relatively accurate, since terraform will generally call FileRead before this.
	if d.HasChange("contents") {
		git_add := exec.Command("git", "add", "--intent-to-add", "--", filepath)
		git_add.Dir = checkout_dir
		if err := git_add.Run(); err != nil {
			return err
		}

		git_commit := exec.Command("git", "commit", "-m", commit_message, "--", filepath)
		git_commit.Dir = checkout_dir
		if err := git_commit.Run(); err != nil {
			return err
		}

		git_push := exec.Command("git", "push", repo, fmt.Sprintf("HEAD:%s", branch))
		git_push.Dir = checkout_dir
		if err := git_push.Run(); err != nil {
			return err
		}
	}
	return nil
}
func FileDelete(d *schema.ResourceData, meta interface{}) error {
	return nil
}

func mungeGitDir(id string) string {
	return b64.URLEncoding.EncodeToString([]byte(id))
}

func shallowSparseGitCheckout(checkout_dir, repo, branch, filepath string) error {
	if err := os.MkdirAll(checkout_dir, 0755); err != nil {
		return err
	}

	// git init appears to be idempotent.
	git_init := exec.Command("git", "init")
	git_init.Dir = checkout_dir
	if err := git_init.Run(); err != nil {
		return err
	}

	git_config := exec.Command("git", "config", "core.sparsecheckout", "true")
	git_config.Dir = checkout_dir
	if err := git_config.Run(); err != nil {
		return err
	}

	if err := ioutil.WriteFile(
		path.Join(checkout_dir, ".git", "info", "sparse-checkout"),
		[]byte(filepath),
		0666,
	); err != nil {
		return err
	}

	git_fetch := exec.Command("git", "fetch", "--depth", "1", repo, branch)
	git_fetch.Dir = checkout_dir
	if err := git_fetch.Run(); err != nil {
		return err
	}

	git_checkout := exec.Command("git", "checkout", "--force", "FETCH_HEAD")
	git_checkout.Dir = checkout_dir
	if err := git_checkout.Run(); err != nil {
		return err
	}

	git_clean := exec.Command("git", "clean", "-ffdx")
	git_clean.Dir = checkout_dir
	if err := git_clean.Run(); err != nil {
		return err
	}

	return nil
}<|endoftext|>


<commit_before>package libkb

import (
	"bytes"
	"fmt"
	"io"
	"os/exec"
	"sync"
)

type GpgCLI struct {
	path    string
	options []string

	// Configuration --- cache the results
	configured     bool
	configExplicit bool
	configError    error

	mutex *sync.Mutex

	logUI LogUI
}

type GpgCLIArg struct {
	LogUI LogUI // If nil, use the global
}

func NewGpgCLI(arg GpgCLIArg) *GpgCLI {
	logUI := arg.LogUI
	if logUI == nil {
		logUI = G.Log
	}
	return &GpgCLI{
		configured: false,
		mutex:      new(sync.Mutex),
		logUI:      logUI,
	}
}

func (g *GpgCLI) Configure() (err error) {

	g.mutex.Lock()
	defer g.mutex.Unlock()

	prog := G.Env.GetGpg()
	opts := G.Env.GetGpgOptions()

	if len(prog) > 0 {
		err = canExec(prog)
	} else {
		prog, err = exec.LookPath("gpg2")
		if err != nil {
			prog, err = exec.LookPath("gpg")
		}
	}

	g.logUI.Debug("| configured GPG w/ path: %s", prog)

	g.path = prog
	g.options = opts

	return
}

// CanExec returns true if a gpg executable exists.
func (g *GpgCLI) CanExec() (bool, error) {
	if err := g.Configure(); err != nil {
		if oerr, ok := err.(*exec.Error); ok {
			if oerr.Err == exec.ErrNotFound {
				return false, nil
			}
		}
		return false, err
	}
	return true, nil
}

// Path returns the path of the gpg executable.  Configure must be
// called before using this.
func (g *GpgCLI) Path() string {
	if !g.configured {
		panic("GpgCLI not configured")
	}
	return g.path
}

type RunGpgArg struct {
	Arguments []string
	Stdin     bool
	Stderr    io.WriteCloser
	Stdout    io.WriteCloser
}

type RunGpgRes struct {
	Stdin io.WriteCloser
	Err   error
	Wait  func() error
}

func (g *GpgCLI) ImportKey(secret bool, fp PGPFingerprint) (ret *PGPKeyBundle, err error) {
	var cmd string
	if secret {
		cmd = "--export-secret-key"
	} else {
		cmd = "--export"
	}

	arg := RunGpg2Arg{
		Arguments: []string{"--armor", cmd, fp.String()},
		Stdout:    true,
	}

	res := g.Run2(arg)
	if res.Err != nil {
		return nil, res.Err
	}

	buf := new(bytes.Buffer)
	buf.ReadFrom(res.Stdout)
	armored := buf.String()

	if err = res.Wait(); err != nil {
		return nil, err
	}

	if len(armored) == 0 {
		return nil, NoKeyError{fmt.Sprintf("No key found for %s", fp)}
	}

	return ReadOneKeyFromString(armored)
}

func (g *GpgCLI) ExportKey(k PGPKeyBundle) (err error) {
	arg := RunGpg2Arg{
		Arguments: []string{"--import"},
		Stdin:     true,
	}

	res := g.Run2(arg)
	if res.Err != nil {
		return res.Err
	}

	e1 := k.EncodeToStream(res.Stdin)
	e2 := res.Stdin.Close()
	e3 := res.Wait()
	return PickFirstError(e1, e2, e3)
}

type RunGpg2Arg struct {
	Arguments []string
	Stdin     bool
	Stderr    bool
	Stdout    bool
}

type RunGpg2Res struct {
	Stdin  io.WriteCloser
	Stdout io.ReadCloser
	Stderr io.ReadCloser
	Wait   func() error
	Err    error
}

func (g *GpgCLI) Run2(arg RunGpg2Arg) (res RunGpg2Res) {

	cmd := g.MakeCmd(arg.Arguments)

	if arg.Stdin {
		if res.Stdin, res.Err = cmd.StdinPipe(); res.Err != nil {
			return
		}
	}

	var stdout, stderr io.ReadCloser

	if stdout, res.Err = cmd.StdoutPipe(); res.Err != nil {
		return
	}
	if stderr, res.Err = cmd.StderrPipe(); res.Err != nil {
		return
	}

	if res.Err = cmd.Start(); res.Err != nil {
		return
	}

	waited := false
	out := 0
	ch := make(chan error)
	var fep FirstErrorPicker

	res.Wait = func() error {
		for out > 0 {
			fep.Push(<-ch)
			out--
		}
		if !waited {
			waited = true
			err := cmd.Wait()
			if err != nil {
				fep.Push(ErrorToGpgError(err))
			}
			return fep.Error()
		}
		return nil
	}

	if !arg.Stdout {
		out++
		go func() {
			ch <- DrainPipe(stdout, func(s string) { g.logUI.Info(s) })
		}()
	} else {
		res.Stdout = stdout
	}

	if !arg.Stderr {
		out++
		go func() {
			ch <- DrainPipe(stderr, func(s string) { g.logUI.Info(s) })
		}()
	} else {
		res.Stderr = stderr
	}

	return
}

func (g *GpgCLI) MakeCmd(args []string) *exec.Cmd {
	var nargs []string
	if g.options != nil {
		nargs = make([]string, len(g.options))
		copy(nargs, g.options)
		nargs = append(nargs, args...)
	} else {
		nargs = args
	}
	g.logUI.Debug("| running Gpg: %s %v", g.path, nargs)
	return exec.Command(g.path, nargs...)
}

func (g *GpgCLI) Run(arg RunGpgArg) (res RunGpgRes) {

	cmd := g.MakeCmd(arg.Arguments)

	waited := false

	var stdout, stderr io.ReadCloser

	if arg.Stdin {
		if res.Stdin, res.Err = cmd.StdinPipe(); res.Err != nil {
			return
		}
	}
	if stdout, res.Err = cmd.StdoutPipe(); res.Err != nil {
		return
	}
	if stderr, res.Err = cmd.StderrPipe(); res.Err != nil {
		return
	}

	if res.Err = cmd.Start(); res.Err != nil {
		return
	}

	waitfn := func() error {
		if !waited {
			waited = true
			return cmd.Wait()
		}
		return nil
	}

	if arg.Stdin {
		res.Wait = waitfn
	} else {
		defer waitfn()
	}

	var e1, e2, e3 error

	if arg.Stdout != nil {
		_, e1 = io.Copy(arg.Stdout, stdout)
	} else {
		e1 = DrainPipe(stdout, func(s string) { g.logUI.Info(s) })
	}

	if arg.Stderr != nil {
		_, e2 = io.Copy(arg.Stderr, stderr)
	} else {
		e2 = DrainPipe(stderr, func(s string) { g.logUI.Warning(s) })
	}

	if !arg.Stdin {
		e3 = waitfn()
	}

	res.Err = PickFirstError(e1, e2, e3)
	return
}
<commit_msg>make importing private keys grab the exact armored bundle, like public keys do<commit_after>package libkb

import (
	"bytes"
	"fmt"
	"io"
	"os/exec"
	"sync"
)

type GpgCLI struct {
	path    string
	options []string

	// Configuration --- cache the results
	configured     bool
	configExplicit bool
	configError    error

	mutex *sync.Mutex

	logUI LogUI
}

type GpgCLIArg struct {
	LogUI LogUI // If nil, use the global
}

func NewGpgCLI(arg GpgCLIArg) *GpgCLI {
	logUI := arg.LogUI
	if logUI == nil {
		logUI = G.Log
	}
	return &GpgCLI{
		configured: false,
		mutex:      new(sync.Mutex),
		logUI:      logUI,
	}
}

func (g *GpgCLI) Configure() (err error) {

	g.mutex.Lock()
	defer g.mutex.Unlock()

	prog := G.Env.GetGpg()
	opts := G.Env.GetGpgOptions()

	if len(prog) > 0 {
		err = canExec(prog)
	} else {
		prog, err = exec.LookPath("gpg2")
		if err != nil {
			prog, err = exec.LookPath("gpg")
		}
	}

	g.logUI.Debug("| configured GPG w/ path: %s", prog)

	g.path = prog
	g.options = opts

	return
}

// CanExec returns true if a gpg executable exists.
func (g *GpgCLI) CanExec() (bool, error) {
	if err := g.Configure(); err != nil {
		if oerr, ok := err.(*exec.Error); ok {
			if oerr.Err == exec.ErrNotFound {
				return false, nil
			}
		}
		return false, err
	}
	return true, nil
}

// Path returns the path of the gpg executable.  Configure must be
// called before using this.
func (g *GpgCLI) Path() string {
	if !g.configured {
		panic("GpgCLI not configured")
	}
	return g.path
}

type RunGpgArg struct {
	Arguments []string
	Stdin     bool
	Stderr    io.WriteCloser
	Stdout    io.WriteCloser
}

type RunGpgRes struct {
	Stdin io.WriteCloser
	Err   error
	Wait  func() error
}

func (g *GpgCLI) ImportKey(secret bool, fp PGPFingerprint) (*PGPKeyBundle, error) {
	var cmd string
	if secret {
		cmd = "--export-secret-key"
	} else {
		cmd = "--export"
	}

	arg := RunGpg2Arg{
		Arguments: []string{"--armor", cmd, fp.String()},
		Stdout:    true,
	}

	res := g.Run2(arg)
	if res.Err != nil {
		return nil, res.Err
	}

	buf := new(bytes.Buffer)
	buf.ReadFrom(res.Stdout)
	armored := buf.String()

	if err := res.Wait(); err != nil {
		return nil, err
	}

	if len(armored) == 0 {
		return nil, NoKeyError{fmt.Sprintf("No key found for %s", fp)}
	}

	bundle, err := ReadOneKeyFromString(armored)
	if err != nil {
		return nil, err
	}

	// For secret keys, *also* import the key in public mode, and then grab the
	// ArmoredPublicKey from that. That's because the public import goes out of
	// its way to preserve the exact armored string from GPG.
	if secret {
		publicBundle, err := g.ImportKey(false, fp)
		if err != nil {
			return nil, err
		}
		bundle.ArmoredPublicKey = publicBundle.ArmoredPublicKey
	}

	return bundle, nil
}

func (g *GpgCLI) ExportKey(k PGPKeyBundle) (err error) {
	arg := RunGpg2Arg{
		Arguments: []string{"--import"},
		Stdin:     true,
	}

	res := g.Run2(arg)
	if res.Err != nil {
		return res.Err
	}

	e1 := k.EncodeToStream(res.Stdin)
	e2 := res.Stdin.Close()
	e3 := res.Wait()
	return PickFirstError(e1, e2, e3)
}

type RunGpg2Arg struct {
	Arguments []string
	Stdin     bool
	Stderr    bool
	Stdout    bool
}

type RunGpg2Res struct {
	Stdin  io.WriteCloser
	Stdout io.ReadCloser
	Stderr io.ReadCloser
	Wait   func() error
	Err    error
}

func (g *GpgCLI) Run2(arg RunGpg2Arg) (res RunGpg2Res) {

	cmd := g.MakeCmd(arg.Arguments)

	if arg.Stdin {
		if res.Stdin, res.Err = cmd.StdinPipe(); res.Err != nil {
			return
		}
	}

	var stdout, stderr io.ReadCloser

	if stdout, res.Err = cmd.StdoutPipe(); res.Err != nil {
		return
	}
	if stderr, res.Err = cmd.StderrPipe(); res.Err != nil {
		return
	}

	if res.Err = cmd.Start(); res.Err != nil {
		return
	}

	waited := false
	out := 0
	ch := make(chan error)
	var fep FirstErrorPicker

	res.Wait = func() error {
		for out > 0 {
			fep.Push(<-ch)
			out--
		}
		if !waited {
			waited = true
			err := cmd.Wait()
			if err != nil {
				fep.Push(ErrorToGpgError(err))
			}
			return fep.Error()
		}
		return nil
	}

	if !arg.Stdout {
		out++
		go func() {
			ch <- DrainPipe(stdout, func(s string) { g.logUI.Info(s) })
		}()
	} else {
		res.Stdout = stdout
	}

	if !arg.Stderr {
		out++
		go func() {
			ch <- DrainPipe(stderr, func(s string) { g.logUI.Info(s) })
		}()
	} else {
		res.Stderr = stderr
	}

	return
}

func (g *GpgCLI) MakeCmd(args []string) *exec.Cmd {
	var nargs []string
	if g.options != nil {
		nargs = make([]string, len(g.options))
		copy(nargs, g.options)
		nargs = append(nargs, args...)
	} else {
		nargs = args
	}
	g.logUI.Debug("| running Gpg: %s %v", g.path, nargs)
	return exec.Command(g.path, nargs...)
}

func (g *GpgCLI) Run(arg RunGpgArg) (res RunGpgRes) {

	cmd := g.MakeCmd(arg.Arguments)

	waited := false

	var stdout, stderr io.ReadCloser

	if arg.Stdin {
		if res.Stdin, res.Err = cmd.StdinPipe(); res.Err != nil {
			return
		}
	}
	if stdout, res.Err = cmd.StdoutPipe(); res.Err != nil {
		return
	}
	if stderr, res.Err = cmd.StderrPipe(); res.Err != nil {
		return
	}

	if res.Err = cmd.Start(); res.Err != nil {
		return
	}

	waitfn := func() error {
		if !waited {
			waited = true
			return cmd.Wait()
		}
		return nil
	}

	if arg.Stdin {
		res.Wait = waitfn
	} else {
		defer waitfn()
	}

	var e1, e2, e3 error

	if arg.Stdout != nil {
		_, e1 = io.Copy(arg.Stdout, stdout)
	} else {
		e1 = DrainPipe(stdout, func(s string) { g.logUI.Info(s) })
	}

	if arg.Stderr != nil {
		_, e2 = io.Copy(arg.Stderr, stderr)
	} else {
		e2 = DrainPipe(stderr, func(s string) { g.logUI.Warning(s) })
	}

	if !arg.Stdin {
		e3 = waitfn()
	}

	res.Err = PickFirstError(e1, e2, e3)
	return
}
<|endoftext|>


<commit_before>package storage

import (
	"code.google.com/p/weed-fs/go/glog"
	"code.google.com/p/weed-fs/go/util"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/url"
	"strconv"
	"strings"
)

type DiskLocation struct {
	directory      string
	maxVolumeCount int
	volumes        map[VolumeId]*Volume
}
type Store struct {
	Port            int
	Ip              string
	PublicUrl       string
	locations       []*DiskLocation
	masterNode      string
	dataCenter      string //optional informaton, overwriting master setting if exists
	rack            string //optional information, overwriting master setting if exists
	connected       bool
	volumeSizeLimit uint64 //read from the master
}

func NewStore(port int, ip, publicUrl string, dirnames []string, maxVolumeCounts []int) (s *Store) {
	s = &Store{Port: port, Ip: ip, PublicUrl: publicUrl}
	s.locations = make([]*DiskLocation, 0)
	for i := 0; i < len(dirnames); i++ {
		location := &DiskLocation{directory: dirnames[i], maxVolumeCount: maxVolumeCounts[i]}
		location.volumes = make(map[VolumeId]*Volume)
		location.loadExistingVolumes()
		s.locations = append(s.locations, location)
	}
	return
}
func (s *Store) AddVolume(volumeListString string, collection string, replicationType string) error {
	rt, e := NewReplicationTypeFromString(replicationType)
	if e != nil {
		return e
	}
	for _, range_string := range strings.Split(volumeListString, ",") {
		if strings.Index(range_string, "-") < 0 {
			id_string := range_string
			id, err := NewVolumeId(id_string)
			if err != nil {
				return fmt.Errorf("Volume Id %s is not a valid unsigned integer!", id_string)
			}
			e = s.addVolume(VolumeId(id), collection, rt)
		} else {
			pair := strings.Split(range_string, "-")
			start, start_err := strconv.ParseUint(pair[0], 10, 64)
			if start_err != nil {
				return fmt.Errorf("Volume Start Id %s is not a valid unsigned integer!", pair[0])
			}
			end, end_err := strconv.ParseUint(pair[1], 10, 64)
			if end_err != nil {
				return fmt.Errorf("Volume End Id %s is not a valid unsigned integer!", pair[1])
			}
			for id := start; id <= end; id++ {
				if err := s.addVolume(VolumeId(id), collection, rt); err != nil {
					e = err
				}
			}
		}
	}
	return e
}
func (s *Store) findVolume(vid VolumeId) *Volume {
	for _, location := range s.locations {
		if v, found := location.volumes[vid]; found {
			return v
		}
	}
	return nil
}
func (s *Store) findFreeLocation() (ret *DiskLocation) {
	max := 0
	for _, location := range s.locations {
		currentFreeCount := location.maxVolumeCount - len(location.volumes)
		if currentFreeCount > max {
			max = currentFreeCount
			ret = location
		}
	}
	return ret
}
func (s *Store) addVolume(vid VolumeId, collection string, replicationType ReplicationType) error {
	if s.findVolume(vid) != nil {
		return fmt.Errorf("Volume Id %s already exists!", vid)
	}
	if location := s.findFreeLocation(); location != nil {
		glog.V(0).Infoln("In dir", location.directory, "adds volume =", vid, ", collection =", collection, ", replicationType =", replicationType)
		if volume, err := NewVolume(location.directory, collection, vid, replicationType); err == nil {
			location.volumes[vid] = volume
			return nil
		} else {
			return err
		}
	}
	return fmt.Errorf("No more free space left")
}

func (s *Store) CheckCompactVolume(volumeIdString string, garbageThresholdString string) (error, bool) {
	vid, err := NewVolumeId(volumeIdString)
	if err != nil {
		return fmt.Errorf("Volume Id %s is not a valid unsigned integer!", volumeIdString), false
	}
	garbageThreshold, e := strconv.ParseFloat(garbageThresholdString, 32)
	if e != nil {
		return fmt.Errorf("garbageThreshold %s is not a valid float number!", garbageThresholdString), false
	}
	if v := s.findVolume(vid); v != nil {
		return nil, garbageThreshold < v.garbageLevel()
	}
	return fmt.Errorf("volume id %s is not found during check compact!", vid), false
}
func (s *Store) CompactVolume(volumeIdString string) error {
	vid, err := NewVolumeId(volumeIdString)
	if err != nil {
		return fmt.Errorf("Volume Id %s is not a valid unsigned integer!", volumeIdString)
	}
	if v := s.findVolume(vid); v != nil {
		return v.Compact()
	}
	return fmt.Errorf("volume id %s is not found during compact!", vid)
}
func (s *Store) CommitCompactVolume(volumeIdString string) error {
	vid, err := NewVolumeId(volumeIdString)
	if err != nil {
		return fmt.Errorf("Volume Id %s is not a valid unsigned integer!", volumeIdString)
	}
	if v := s.findVolume(vid); v != nil {
		return v.commitCompact()
	}
	return fmt.Errorf("volume id %s is not found during commit compact!", vid)
}
func (s *Store) FreezeVolume(volumeIdString string) error {
	vid, err := NewVolumeId(volumeIdString)
	if err != nil {
		return fmt.Errorf("Volume Id %s is not a valid unsigned integer!", volumeIdString)
	}
	if v := s.findVolume(vid); v != nil {
		if v.readOnly {
			return fmt.Errorf("Volume %s is already read-only", volumeIdString)
		}
		return v.freeze()
	}
	return fmt.Errorf("volume id %s is not found during freeze!", vid)
}
func (l *DiskLocation) loadExistingVolumes() {
	if dirs, err := ioutil.ReadDir(l.directory); err == nil {
		for _, dir := range dirs {
			name := dir.Name()
			if !dir.IsDir() && strings.HasSuffix(name, ".dat") {
				collection := ""
				base := name[:len(name)-len(".dat")]
				i := strings.Index(base, "_")
				if i > 0 {
					collection, base = base[0:i], base[i+1:]
				}
				if vid, err := NewVolumeId(base); err == nil {
					if l.volumes[vid] == nil {
						if v, e := NewVolume(l.directory, collection, vid, CopyNil); e == nil {
							l.volumes[vid] = v
							glog.V(0).Infoln("data file", l.directory+"/"+name, "replicationType =", v.ReplicaType, "version =", v.Version(), "size =", v.Size())
						}
					}
				}
			}
		}
	}
	glog.V(0).Infoln("Store started on dir:", l.directory, "with", len(l.volumes), "volumes", "max", l.maxVolumeCount)
}
func (s *Store) Status() []*VolumeInfo {
	var stats []*VolumeInfo
	for _, location := range s.locations {
		for k, v := range location.volumes {
			s := &VolumeInfo{Id: VolumeId(k), Size: v.ContentSize(),
				Collection:       v.Collection,
				RepType:          v.ReplicaType,
				Version:          v.Version(),
				FileCount:        v.nm.FileCount(),
				DeleteCount:      v.nm.DeletedCount(),
				DeletedByteCount: v.nm.DeletedSize(),
				ReadOnly:         v.readOnly}
			stats = append(stats, s)
		}
	}
	return stats
}

type JoinResult struct {
	VolumeSizeLimit uint64
}

func (s *Store) SetMaster(mserver string) {
	s.masterNode = mserver
}
func (s *Store) SetDataCenter(dataCenter string) {
	s.dataCenter = dataCenter
}
func (s *Store) SetRack(rack string) {
	s.rack = rack
}
func (s *Store) Join() error {
	stats := new([]*VolumeInfo)
	maxVolumeCount := 0
	for _, location := range s.locations {
		maxVolumeCount = maxVolumeCount + location.maxVolumeCount
		for k, v := range location.volumes {
			s := &VolumeInfo{Id: VolumeId(k), Size: uint64(v.Size()),
				Collection:       v.Collection,
				RepType:          v.ReplicaType,
				Version:          v.Version(),
				FileCount:        v.nm.FileCount(),
				DeleteCount:      v.nm.DeletedCount(),
				DeletedByteCount: v.nm.DeletedSize(),
				ReadOnly:         v.readOnly}
			*stats = append(*stats, s)
		}
	}
	bytes, _ := json.Marshal(stats)
	values := make(url.Values)
	if !s.connected {
		values.Add("init", "true")
	}
	values.Add("port", strconv.Itoa(s.Port))
	values.Add("ip", s.Ip)
	values.Add("publicUrl", s.PublicUrl)
	values.Add("volumes", string(bytes))
	values.Add("maxVolumeCount", strconv.Itoa(maxVolumeCount))
	values.Add("dataCenter", s.dataCenter)
	values.Add("rack", s.rack)
	jsonBlob, err := util.Post("http://"+s.masterNode+"/dir/join", values)
	if err != nil {
		return err
	}
	var ret JoinResult
	if err := json.Unmarshal(jsonBlob, &ret); err != nil {
		return err
	}
	s.volumeSizeLimit = ret.VolumeSizeLimit
	s.connected = true
	return nil
}
func (s *Store) Close() {
	for _, location := range s.locations {
		for _, v := range location.volumes {
			v.Close()
		}
	}
}
func (s *Store) Write(i VolumeId, n *Needle) (size uint32, err error) {
	if v := s.findVolume(i); v != nil {
		if v.readOnly {
			err = fmt.Errorf("Volume %s is read only!", i)
			return
		} else {
			if MaxPossibleVolumeSize >= v.ContentSize()+uint64(size) {
				size, err = v.write(n)
			} else {
				err = fmt.Errorf("Volume Size Limit %d Exceeded! Current size is %d", s.volumeSizeLimit, v.ContentSize())
			}
			if s.volumeSizeLimit < v.ContentSize()+uint64(size) {
				glog.V(0).Infoln("volume", i, "size", v.ContentSize(), "will exceed limit", s.volumeSizeLimit)
				if e = s.Join(); e != nil {
					glog.V(0).Infoln("error when reporting size:", e)
				}
			}
		}
		return
	}
	glog.V(0).Infoln("volume", i, "not found!")
	err = fmt.Errorf("Volume %s not found!", i)
	return
}
func (s *Store) Delete(i VolumeId, n *Needle) (uint32, error) {
	if v := s.findVolume(i); v != nil && !v.readOnly {
		return v.delete(n)
	}
	return 0, nil
}
func (s *Store) Read(i VolumeId, n *Needle) (int, error) {
	if v := s.findVolume(i); v != nil {
		return v.read(n)
	}
	return 0, fmt.Errorf("Volume %s not found!", i)
}
func (s *Store) GetVolume(i VolumeId) *Volume {
	return s.findVolume(i)
}

func (s *Store) HasVolume(i VolumeId) bool {
	v := s.findVolume(i)
	return v != nil
}
<commit_msg>report when size is closing to the volume limit fix error<commit_after>package storage

import (
	"code.google.com/p/weed-fs/go/glog"
	"code.google.com/p/weed-fs/go/util"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/url"
	"strconv"
	"strings"
)

type DiskLocation struct {
	directory      string
	maxVolumeCount int
	volumes        map[VolumeId]*Volume
}
type Store struct {
	Port            int
	Ip              string
	PublicUrl       string
	locations       []*DiskLocation
	masterNode      string
	dataCenter      string //optional informaton, overwriting master setting if exists
	rack            string //optional information, overwriting master setting if exists
	connected       bool
	volumeSizeLimit uint64 //read from the master
}

func NewStore(port int, ip, publicUrl string, dirnames []string, maxVolumeCounts []int) (s *Store) {
	s = &Store{Port: port, Ip: ip, PublicUrl: publicUrl}
	s.locations = make([]*DiskLocation, 0)
	for i := 0; i < len(dirnames); i++ {
		location := &DiskLocation{directory: dirnames[i], maxVolumeCount: maxVolumeCounts[i]}
		location.volumes = make(map[VolumeId]*Volume)
		location.loadExistingVolumes()
		s.locations = append(s.locations, location)
	}
	return
}
func (s *Store) AddVolume(volumeListString string, collection string, replicationType string) error {
	rt, e := NewReplicationTypeFromString(replicationType)
	if e != nil {
		return e
	}
	for _, range_string := range strings.Split(volumeListString, ",") {
		if strings.Index(range_string, "-") < 0 {
			id_string := range_string
			id, err := NewVolumeId(id_string)
			if err != nil {
				return fmt.Errorf("Volume Id %s is not a valid unsigned integer!", id_string)
			}
			e = s.addVolume(VolumeId(id), collection, rt)
		} else {
			pair := strings.Split(range_string, "-")
			start, start_err := strconv.ParseUint(pair[0], 10, 64)
			if start_err != nil {
				return fmt.Errorf("Volume Start Id %s is not a valid unsigned integer!", pair[0])
			}
			end, end_err := strconv.ParseUint(pair[1], 10, 64)
			if end_err != nil {
				return fmt.Errorf("Volume End Id %s is not a valid unsigned integer!", pair[1])
			}
			for id := start; id <= end; id++ {
				if err := s.addVolume(VolumeId(id), collection, rt); err != nil {
					e = err
				}
			}
		}
	}
	return e
}
func (s *Store) findVolume(vid VolumeId) *Volume {
	for _, location := range s.locations {
		if v, found := location.volumes[vid]; found {
			return v
		}
	}
	return nil
}
func (s *Store) findFreeLocation() (ret *DiskLocation) {
	max := 0
	for _, location := range s.locations {
		currentFreeCount := location.maxVolumeCount - len(location.volumes)
		if currentFreeCount > max {
			max = currentFreeCount
			ret = location
		}
	}
	return ret
}
func (s *Store) addVolume(vid VolumeId, collection string, replicationType ReplicationType) error {
	if s.findVolume(vid) != nil {
		return fmt.Errorf("Volume Id %s already exists!", vid)
	}
	if location := s.findFreeLocation(); location != nil {
		glog.V(0).Infoln("In dir", location.directory, "adds volume =", vid, ", collection =", collection, ", replicationType =", replicationType)
		if volume, err := NewVolume(location.directory, collection, vid, replicationType); err == nil {
			location.volumes[vid] = volume
			return nil
		} else {
			return err
		}
	}
	return fmt.Errorf("No more free space left")
}

func (s *Store) CheckCompactVolume(volumeIdString string, garbageThresholdString string) (error, bool) {
	vid, err := NewVolumeId(volumeIdString)
	if err != nil {
		return fmt.Errorf("Volume Id %s is not a valid unsigned integer!", volumeIdString), false
	}
	garbageThreshold, e := strconv.ParseFloat(garbageThresholdString, 32)
	if e != nil {
		return fmt.Errorf("garbageThreshold %s is not a valid float number!", garbageThresholdString), false
	}
	if v := s.findVolume(vid); v != nil {
		return nil, garbageThreshold < v.garbageLevel()
	}
	return fmt.Errorf("volume id %s is not found during check compact!", vid), false
}
func (s *Store) CompactVolume(volumeIdString string) error {
	vid, err := NewVolumeId(volumeIdString)
	if err != nil {
		return fmt.Errorf("Volume Id %s is not a valid unsigned integer!", volumeIdString)
	}
	if v := s.findVolume(vid); v != nil {
		return v.Compact()
	}
	return fmt.Errorf("volume id %s is not found during compact!", vid)
}
func (s *Store) CommitCompactVolume(volumeIdString string) error {
	vid, err := NewVolumeId(volumeIdString)
	if err != nil {
		return fmt.Errorf("Volume Id %s is not a valid unsigned integer!", volumeIdString)
	}
	if v := s.findVolume(vid); v != nil {
		return v.commitCompact()
	}
	return fmt.Errorf("volume id %s is not found during commit compact!", vid)
}
func (s *Store) FreezeVolume(volumeIdString string) error {
	vid, err := NewVolumeId(volumeIdString)
	if err != nil {
		return fmt.Errorf("Volume Id %s is not a valid unsigned integer!", volumeIdString)
	}
	if v := s.findVolume(vid); v != nil {
		if v.readOnly {
			return fmt.Errorf("Volume %s is already read-only", volumeIdString)
		}
		return v.freeze()
	}
	return fmt.Errorf("volume id %s is not found during freeze!", vid)
}
func (l *DiskLocation) loadExistingVolumes() {
	if dirs, err := ioutil.ReadDir(l.directory); err == nil {
		for _, dir := range dirs {
			name := dir.Name()
			if !dir.IsDir() && strings.HasSuffix(name, ".dat") {
				collection := ""
				base := name[:len(name)-len(".dat")]
				i := strings.Index(base, "_")
				if i > 0 {
					collection, base = base[0:i], base[i+1:]
				}
				if vid, err := NewVolumeId(base); err == nil {
					if l.volumes[vid] == nil {
						if v, e := NewVolume(l.directory, collection, vid, CopyNil); e == nil {
							l.volumes[vid] = v
							glog.V(0).Infoln("data file", l.directory+"/"+name, "replicationType =", v.ReplicaType, "version =", v.Version(), "size =", v.Size())
						}
					}
				}
			}
		}
	}
	glog.V(0).Infoln("Store started on dir:", l.directory, "with", len(l.volumes), "volumes", "max", l.maxVolumeCount)
}
func (s *Store) Status() []*VolumeInfo {
	var stats []*VolumeInfo
	for _, location := range s.locations {
		for k, v := range location.volumes {
			s := &VolumeInfo{Id: VolumeId(k), Size: v.ContentSize(),
				Collection:       v.Collection,
				RepType:          v.ReplicaType,
				Version:          v.Version(),
				FileCount:        v.nm.FileCount(),
				DeleteCount:      v.nm.DeletedCount(),
				DeletedByteCount: v.nm.DeletedSize(),
				ReadOnly:         v.readOnly}
			stats = append(stats, s)
		}
	}
	return stats
}

type JoinResult struct {
	VolumeSizeLimit uint64
}

func (s *Store) SetMaster(mserver string) {
	s.masterNode = mserver
}
func (s *Store) SetDataCenter(dataCenter string) {
	s.dataCenter = dataCenter
}
func (s *Store) SetRack(rack string) {
	s.rack = rack
}
func (s *Store) Join() error {
	stats := new([]*VolumeInfo)
	maxVolumeCount := 0
	for _, location := range s.locations {
		maxVolumeCount = maxVolumeCount + location.maxVolumeCount
		for k, v := range location.volumes {
			s := &VolumeInfo{Id: VolumeId(k), Size: uint64(v.Size()),
				Collection:       v.Collection,
				RepType:          v.ReplicaType,
				Version:          v.Version(),
				FileCount:        v.nm.FileCount(),
				DeleteCount:      v.nm.DeletedCount(),
				DeletedByteCount: v.nm.DeletedSize(),
				ReadOnly:         v.readOnly}
			*stats = append(*stats, s)
		}
	}
	bytes, _ := json.Marshal(stats)
	values := make(url.Values)
	if !s.connected {
		values.Add("init", "true")
	}
	values.Add("port", strconv.Itoa(s.Port))
	values.Add("ip", s.Ip)
	values.Add("publicUrl", s.PublicUrl)
	values.Add("volumes", string(bytes))
	values.Add("maxVolumeCount", strconv.Itoa(maxVolumeCount))
	values.Add("dataCenter", s.dataCenter)
	values.Add("rack", s.rack)
	jsonBlob, err := util.Post("http://"+s.masterNode+"/dir/join", values)
	if err != nil {
		return err
	}
	var ret JoinResult
	if err := json.Unmarshal(jsonBlob, &ret); err != nil {
		return err
	}
	s.volumeSizeLimit = ret.VolumeSizeLimit
	s.connected = true
	return nil
}
func (s *Store) Close() {
	for _, location := range s.locations {
		for _, v := range location.volumes {
			v.Close()
		}
	}
}
func (s *Store) Write(i VolumeId, n *Needle) (size uint32, err error) {
	if v := s.findVolume(i); v != nil {
		if v.readOnly {
			err = fmt.Errorf("Volume %s is read only!", i)
			return
		} else {
			if MaxPossibleVolumeSize >= v.ContentSize()+uint64(size) {
				size, err = v.write(n)
			} else {
				err = fmt.Errorf("Volume Size Limit %d Exceeded! Current size is %d", s.volumeSizeLimit, v.ContentSize())
			}
			if s.volumeSizeLimit < v.ContentSize()+3*uint64(size) {
				glog.V(0).Infoln("volume", i, "size", v.ContentSize(), "will exceed limit", s.volumeSizeLimit)
				if e := s.Join(); e != nil {
					glog.V(0).Infoln("error when reporting size:", e)
				}
			}
		}
		return
	}
	glog.V(0).Infoln("volume", i, "not found!")
	err = fmt.Errorf("Volume %s not found!", i)
	return
}
func (s *Store) Delete(i VolumeId, n *Needle) (uint32, error) {
	if v := s.findVolume(i); v != nil && !v.readOnly {
		return v.delete(n)
	}
	return 0, nil
}
func (s *Store) Read(i VolumeId, n *Needle) (int, error) {
	if v := s.findVolume(i); v != nil {
		return v.read(n)
	}
	return 0, fmt.Errorf("Volume %s not found!", i)
}
func (s *Store) GetVolume(i VolumeId) *Volume {
	return s.findVolume(i)
}

func (s *Store) HasVolume(i VolumeId) bool {
	v := s.findVolume(i)
	return v != nil
}
<|endoftext|>


<commit_before>package teams

import (
	"context"
	"fmt"

	"github.com/keybase/client/go/engine"
	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/protocol/keybase1"
)

func HandleRotateRequest(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, generation keybase1.PerTeamKeyGeneration) (err error) {

	ctx = libkb.WithLogTag(ctx, "CLKR")
	defer g.CTrace(ctx, fmt.Sprintf("HandleRotateRequest(%s,%d)", teamID, generation), func() error { return err })()

	return RetryOnSigOldSeqnoError(ctx, g, func(ctx context.Context, _ int) error {
		team, err := Load(ctx, g, keybase1.LoadTeamArg{
			ID:          teamID,
			Public:      teamID.IsPublic(),
			ForceRepoll: true,
		})
		if err != nil {
			return err
		}

		if team.Generation() > generation {
			g.Log.CDebugf(ctx, "current team generation %d > team.clkr generation %d, not rotating", team.Generation(), generation)
			return nil
		}

		g.Log.CDebugf(ctx, "rotating team %s (%s)", team.Name(), teamID)
		if err := team.Rotate(ctx); err != nil {
			g.Log.CDebugf(ctx, "rotating team %s (%s) error: %s", team.Name(), teamID, err)
			return err
		}

		g.Log.CDebugf(ctx, "sucess rotating team %s (%s)", team.Name(), teamID)
		return nil
	})
}

func reloadLocal(ctx context.Context, g *libkb.GlobalContext, row keybase1.TeamChangeRow, change keybase1.TeamChangeSet) (*Team, error) {
	forceRepoll := true
	if change.Renamed {
		// This force reloads the team as a side effect
		err := g.GetTeamLoader().NotifyTeamRename(ctx, row.Id, row.Name)
		if err != nil {
			return nil, err
		}
		forceRepoll = false
	}

	team, err := Load(ctx, g, keybase1.LoadTeamArg{
		ID:          row.Id,
		Public:      row.Id.IsPublic(),
		ForceRepoll: forceRepoll,
	})
	return team, err
}

func handleChangeSingle(ctx context.Context, g *libkb.GlobalContext, row keybase1.TeamChangeRow, change keybase1.TeamChangeSet) (err error) {
	change.KeyRotated = row.KeyRotated
	change.MembershipChanged = row.MembershipChanged

	defer g.CTrace(ctx, fmt.Sprintf("team.handleChangeSingle(%+v, %+v)", row, change), func() error { return err })()

	team, err := reloadLocal(ctx, g, row, change)
	if err != nil {
		return err
	}
	g.NotifyRouter.HandleTeamChanged(ctx, team.ID, team.Name().String(), team.chain().GetLatestSeqno(), change)
	return nil
}

func HandleChangeNotification(ctx context.Context, g *libkb.GlobalContext, rows []keybase1.TeamChangeRow, changes keybase1.TeamChangeSet) (err error) {
	ctx = libkb.WithLogTag(ctx, "CLKR")
	defer g.CTrace(ctx, "HandleChangeNotification", func() error { return err })()
	for _, row := range rows {
		if err := handleChangeSingle(ctx, g, row, changes); err != nil {
			return err
		}
	}
	return nil
}

func HandleDeleteNotification(ctx context.Context, g *libkb.GlobalContext, rows []keybase1.TeamChangeRow) (err error) {
	defer g.CTrace(ctx, fmt.Sprintf("team.HandleDeleteNotification(%v)", len(rows)), func() error { return err })()

	for _, row := range rows {
		g.Log.CDebugf(ctx, "team.HandleDeleteNotification: (%+v)", row)
		err := g.GetTeamLoader().Delete(ctx, row.Id)
		if err != nil {
			g.Log.CDebugf(ctx, "team.HandleDeleteNotification: error deleting team cache: %v", err)
		}
		g.NotifyRouter.HandleTeamDeleted(ctx, row.Id)
	}
	return nil
}

func HandleExitNotification(ctx context.Context, g *libkb.GlobalContext, rows []keybase1.TeamExitRow) (err error) {
	defer g.CTrace(ctx, fmt.Sprintf("team.HandleExitNotification(%v)", len(rows)), func() error { return err })()

	for _, row := range rows {
		g.Log.CDebugf(ctx, "team.HandleExitNotification: (%+v)", row)
		err := g.GetTeamLoader().Delete(ctx, row.Id)
		if err != nil {
			g.Log.CDebugf(ctx, "team.HandleExitNotification: error deleting team cache: %v", err)
		}
		g.NotifyRouter.HandleTeamExit(ctx, row.Id)
	}
	return nil
}

func HandleSBSRequest(ctx context.Context, g *libkb.GlobalContext, msg keybase1.TeamSBSMsg) (err error) {
	ctx = libkb.WithLogTag(ctx, "CLKR")
	defer g.CTrace(ctx, "HandleSBSRequest", func() error { return err })()
	for _, invitee := range msg.Invitees {
		if err := handleSBSSingle(ctx, g, msg.TeamID, invitee); err != nil {
			return err
		}
	}
	return nil
}

func handleSBSSingle(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, untrustedInviteeFromGregor keybase1.TeamInvitee) (err error) {
	defer g.CTrace(ctx, fmt.Sprintf("team.handleSBSSingle(teamID: %v, invitee: %+v)", teamID, untrustedInviteeFromGregor), func() error { return err })()
	uv := NewUserVersion(untrustedInviteeFromGregor.Uid, untrustedInviteeFromGregor.EldestSeqno)
	req, err := reqFromRole(uv, untrustedInviteeFromGregor.Role)
	if err != nil {
		return err
	}
	req.CompletedInvites = make(map[keybase1.TeamInviteID]keybase1.UserVersionPercentForm)
	req.CompletedInvites[untrustedInviteeFromGregor.InviteID] = uv.PercentForm()

	return RetryOnSigOldSeqnoError(ctx, g, func(ctx context.Context, _ int) error {
		team, err := Load(ctx, g, keybase1.LoadTeamArg{
			ID:          teamID,
			Public:      teamID.IsPublic(),
			ForceRepoll: true,
		})
		if err != nil {
			return err
		}

		// verify the invite info:

		// find the invite in the team chain
		invite, found := team.chain().FindActiveInviteByID(untrustedInviteeFromGregor.InviteID)
		if !found {
			return libkb.NotFoundError{}
		}
		category, err := invite.Type.C()
		if err != nil {
			return err
		}
		switch category {
		case keybase1.TeamInviteCategory_SBS:
			//  resolve assertion in link (with uid in invite msg)
			ityp, err := invite.Type.String()
			if err != nil {
				return err
			}
			assertion := fmt.Sprintf("%s@%s+uid:%s", string(invite.Name), ityp, untrustedInviteeFromGregor.Uid)

			arg := keybase1.Identify2Arg{
				UserAssertion:    assertion,
				UseDelegateUI:    false,
				Reason:           keybase1.IdentifyReason{Reason: "process team invite"},
				CanSuppressUI:    true,
				IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_GUI,
			}
			ectx := &engine.Context{
				NetContext: ctx,
			}
			eng := engine.NewResolveThenIdentify2(g, &arg)
			if err := engine.RunEngine(eng, ectx); err != nil {
				return err
			}
		case keybase1.TeamInviteCategory_EMAIL:
			// nothing to verify, need to trust the server
		case keybase1.TeamInviteCategory_KEYBASE:
			if err := assertCanAcceptKeybaseInvite(ctx, g, untrustedInviteeFromGregor, invite); err != nil {
				return err
			}
		default:
			return fmt.Errorf("no verification implemented for invite category %s (%+v)", category, invite)
		}

		g.Log.CDebugf(ctx, "checks passed, proceeding with team.ChangeMembership, req = %+v", req)

		if err = team.ChangeMembership(ctx, req); err != nil {
			return err
		}

		// Send chat welcome message
		g.Log.CDebugf(ctx, "sending welcome message for successful SBS handle")
		SendChatInviteWelcomeMessage(ctx, g, team.Name().String(), category, invite.Inviter.Uid,
			untrustedInviteeFromGregor.Uid)

		return nil
	})
}

func assertCanAcceptKeybaseInvite(ctx context.Context, g *libkb.GlobalContext, untrustedInviteeFromGregor keybase1.TeamInvitee, chainInvite keybase1.TeamInvite) error {
	chainUV, err := chainInvite.KeybaseUserVersion()
	if err != nil {
		return err
	}
	if chainUV.Uid.NotEqual(untrustedInviteeFromGregor.Uid) {
		return fmt.Errorf("chain keybase invite link uid %s does not match uid %s in team.sbs message", chainUV.Uid, untrustedInviteeFromGregor.Uid)
	}

	if chainUV.EldestSeqno.Eq(untrustedInviteeFromGregor.EldestSeqno) {
		return nil
	}

	if chainUV.EldestSeqno == 0 {
		g.Log.CDebugf(ctx, "team.sbs invitee eldest seqno: %d, allowing it to take the invite for eldest seqno 0 (reset account)", untrustedInviteeFromGregor.EldestSeqno)
		return nil
	}

	return fmt.Errorf("chain keybase invite link eldest seqno %d does not match eldest seqno %d in team.sbs message", chainUV.EldestSeqno, untrustedInviteeFromGregor.EldestSeqno)
}

func HandleOpenTeamAccessRequest(ctx context.Context, g *libkb.GlobalContext, msg keybase1.TeamOpenReqMsg) (err error) {
	ctx = libkb.WithLogTag(ctx, "CLKR")
	defer g.CTrace(ctx, "HandleOpenTeamAccessRequest", func() error { return err })()

	return RetryOnSigOldSeqnoError(ctx, g, func(ctx context.Context, _ int) error {
		team, err := Load(ctx, g, keybase1.LoadTeamArg{
			ID:          msg.TeamID,
			Public:      msg.TeamID.IsPublic(),
			ForceRepoll: true,
		})
		if err != nil {
			return err
		}

		if !team.IsOpen() {
			g.Log.CDebugf(ctx, "team %q is not an open team", team.Name)
			return nil // Not an error - let the handler dismiss the message.
		}

		var req keybase1.TeamChangeReq
		role := team.chain().inner.OpenTeamJoinAs

		for _, tar := range msg.Tars {
			uv := NewUserVersion(tar.Uid, tar.EldestSeqno)
			currentRole, err := team.MemberRole(ctx, uv)
			if err != nil {
				return err
			}

			if currentRole.IsOrAbove(role) {
				continue
			}

			switch role {
			case keybase1.TeamRole_READER:
				req.Readers = append(req.Readers, uv)
			case keybase1.TeamRole_WRITER:
				req.Writers = append(req.Writers, uv)
			default:
				return fmt.Errorf("Unexpected role to add to open team: %v", role)
			}
		}

		return team.ChangeMembership(ctx, req)
	})
}

func HandleTeamSeitan(ctx context.Context, g *libkb.GlobalContext, msg keybase1.TeamSeitanMsg) (err error) {
	ctx = libkb.WithLogTag(ctx, "CLKR")
	defer g.CTrace(ctx, "HandleTeamSeitan", func() error { return err })()

	team, err := Load(ctx, g, keybase1.LoadTeamArg{
		ID:          msg.TeamID,
		Public:      msg.TeamID.IsPublic(),
		ForceRepoll: true,
	})
	if err != nil {
		return err
	}

	var req keybase1.TeamChangeReq
	req.CompletedInvites = make(map[keybase1.TeamInviteID]keybase1.UserVersionPercentForm)

	for _, seitan := range msg.Seitans {
		invite, found := team.chain().FindActiveInviteByID(seitan.InviteID)
		if !found {
			return libkb.NotFoundError{}
		}

		peikey, err := SeitanDecodePEIKey(string(invite.Name))
		if err != nil {
			return err
		}

		ikey, err := peikey.DecryptIKey(ctx, team)
		if err != nil {
			return err
		}

		sikey, err := ikey.GenerateSIKey()
		if err != nil {
			return err
		}

		_, encoded, err := sikey.GenerateAcceptanceKey(seitan.Uid, seitan.EldestSeqno, seitan.UnixCTime)
		if err != nil {
			return err
		}

		if seitan.Akey != encoded {
			return fmt.Errorf("did not end up with the same AKey")
		}

		uv := NewUserVersion(seitan.Uid, seitan.EldestSeqno)
		currentRole, err := team.MemberRole(ctx, uv)
		if err != nil {
			return err
		}

		if currentRole.IsOrAbove(invite.Role) {
			continue
		}

		switch invite.Role {
		case keybase1.TeamRole_READER:
			req.Readers = append(req.Readers, uv)
		case keybase1.TeamRole_WRITER:
			req.Writers = append(req.Writers, uv)
		case keybase1.TeamRole_ADMIN:
			req.Readers = append(req.Admins, uv)
		case keybase1.TeamRole_OWNER:
			req.Writers = append(req.Owners, uv)
		default:
			return fmt.Errorf("Unexpected role in invitation: %v", invite.Role)
		}

		req.CompletedInvites[seitan.InviteID] = uv.PercentForm()
	}

	return team.ChangeMembership(ctx, req)
}
<commit_msg>Use secure comparsion when handling seitan requests<commit_after>package teams

import (
	"context"
	"fmt"

	"encoding/base64"

	"github.com/keybase/client/go/engine"
	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/protocol/keybase1"
)

func HandleRotateRequest(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, generation keybase1.PerTeamKeyGeneration) (err error) {

	ctx = libkb.WithLogTag(ctx, "CLKR")
	defer g.CTrace(ctx, fmt.Sprintf("HandleRotateRequest(%s,%d)", teamID, generation), func() error { return err })()

	return RetryOnSigOldSeqnoError(ctx, g, func(ctx context.Context, _ int) error {
		team, err := Load(ctx, g, keybase1.LoadTeamArg{
			ID:          teamID,
			Public:      teamID.IsPublic(),
			ForceRepoll: true,
		})
		if err != nil {
			return err
		}

		if team.Generation() > generation {
			g.Log.CDebugf(ctx, "current team generation %d > team.clkr generation %d, not rotating", team.Generation(), generation)
			return nil
		}

		g.Log.CDebugf(ctx, "rotating team %s (%s)", team.Name(), teamID)
		if err := team.Rotate(ctx); err != nil {
			g.Log.CDebugf(ctx, "rotating team %s (%s) error: %s", team.Name(), teamID, err)
			return err
		}

		g.Log.CDebugf(ctx, "sucess rotating team %s (%s)", team.Name(), teamID)
		return nil
	})
}

func reloadLocal(ctx context.Context, g *libkb.GlobalContext, row keybase1.TeamChangeRow, change keybase1.TeamChangeSet) (*Team, error) {
	forceRepoll := true
	if change.Renamed {
		// This force reloads the team as a side effect
		err := g.GetTeamLoader().NotifyTeamRename(ctx, row.Id, row.Name)
		if err != nil {
			return nil, err
		}
		forceRepoll = false
	}

	team, err := Load(ctx, g, keybase1.LoadTeamArg{
		ID:          row.Id,
		Public:      row.Id.IsPublic(),
		ForceRepoll: forceRepoll,
	})
	return team, err
}

func handleChangeSingle(ctx context.Context, g *libkb.GlobalContext, row keybase1.TeamChangeRow, change keybase1.TeamChangeSet) (err error) {
	change.KeyRotated = row.KeyRotated
	change.MembershipChanged = row.MembershipChanged

	defer g.CTrace(ctx, fmt.Sprintf("team.handleChangeSingle(%+v, %+v)", row, change), func() error { return err })()

	team, err := reloadLocal(ctx, g, row, change)
	if err != nil {
		return err
	}
	g.NotifyRouter.HandleTeamChanged(ctx, team.ID, team.Name().String(), team.chain().GetLatestSeqno(), change)
	return nil
}

func HandleChangeNotification(ctx context.Context, g *libkb.GlobalContext, rows []keybase1.TeamChangeRow, changes keybase1.TeamChangeSet) (err error) {
	ctx = libkb.WithLogTag(ctx, "CLKR")
	defer g.CTrace(ctx, "HandleChangeNotification", func() error { return err })()
	for _, row := range rows {
		if err := handleChangeSingle(ctx, g, row, changes); err != nil {
			return err
		}
	}
	return nil
}

func HandleDeleteNotification(ctx context.Context, g *libkb.GlobalContext, rows []keybase1.TeamChangeRow) (err error) {
	defer g.CTrace(ctx, fmt.Sprintf("team.HandleDeleteNotification(%v)", len(rows)), func() error { return err })()

	for _, row := range rows {
		g.Log.CDebugf(ctx, "team.HandleDeleteNotification: (%+v)", row)
		err := g.GetTeamLoader().Delete(ctx, row.Id)
		if err != nil {
			g.Log.CDebugf(ctx, "team.HandleDeleteNotification: error deleting team cache: %v", err)
		}
		g.NotifyRouter.HandleTeamDeleted(ctx, row.Id)
	}
	return nil
}

func HandleExitNotification(ctx context.Context, g *libkb.GlobalContext, rows []keybase1.TeamExitRow) (err error) {
	defer g.CTrace(ctx, fmt.Sprintf("team.HandleExitNotification(%v)", len(rows)), func() error { return err })()

	for _, row := range rows {
		g.Log.CDebugf(ctx, "team.HandleExitNotification: (%+v)", row)
		err := g.GetTeamLoader().Delete(ctx, row.Id)
		if err != nil {
			g.Log.CDebugf(ctx, "team.HandleExitNotification: error deleting team cache: %v", err)
		}
		g.NotifyRouter.HandleTeamExit(ctx, row.Id)
	}
	return nil
}

func HandleSBSRequest(ctx context.Context, g *libkb.GlobalContext, msg keybase1.TeamSBSMsg) (err error) {
	ctx = libkb.WithLogTag(ctx, "CLKR")
	defer g.CTrace(ctx, "HandleSBSRequest", func() error { return err })()
	for _, invitee := range msg.Invitees {
		if err := handleSBSSingle(ctx, g, msg.TeamID, invitee); err != nil {
			return err
		}
	}
	return nil
}

func handleSBSSingle(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, untrustedInviteeFromGregor keybase1.TeamInvitee) (err error) {
	defer g.CTrace(ctx, fmt.Sprintf("team.handleSBSSingle(teamID: %v, invitee: %+v)", teamID, untrustedInviteeFromGregor), func() error { return err })()
	uv := NewUserVersion(untrustedInviteeFromGregor.Uid, untrustedInviteeFromGregor.EldestSeqno)
	req, err := reqFromRole(uv, untrustedInviteeFromGregor.Role)
	if err != nil {
		return err
	}
	req.CompletedInvites = make(map[keybase1.TeamInviteID]keybase1.UserVersionPercentForm)
	req.CompletedInvites[untrustedInviteeFromGregor.InviteID] = uv.PercentForm()

	return RetryOnSigOldSeqnoError(ctx, g, func(ctx context.Context, _ int) error {
		team, err := Load(ctx, g, keybase1.LoadTeamArg{
			ID:          teamID,
			Public:      teamID.IsPublic(),
			ForceRepoll: true,
		})
		if err != nil {
			return err
		}

		// verify the invite info:

		// find the invite in the team chain
		invite, found := team.chain().FindActiveInviteByID(untrustedInviteeFromGregor.InviteID)
		if !found {
			return libkb.NotFoundError{}
		}
		category, err := invite.Type.C()
		if err != nil {
			return err
		}
		switch category {
		case keybase1.TeamInviteCategory_SBS:
			//  resolve assertion in link (with uid in invite msg)
			ityp, err := invite.Type.String()
			if err != nil {
				return err
			}
			assertion := fmt.Sprintf("%s@%s+uid:%s", string(invite.Name), ityp, untrustedInviteeFromGregor.Uid)

			arg := keybase1.Identify2Arg{
				UserAssertion:    assertion,
				UseDelegateUI:    false,
				Reason:           keybase1.IdentifyReason{Reason: "process team invite"},
				CanSuppressUI:    true,
				IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_GUI,
			}
			ectx := &engine.Context{
				NetContext: ctx,
			}
			eng := engine.NewResolveThenIdentify2(g, &arg)
			if err := engine.RunEngine(eng, ectx); err != nil {
				return err
			}
		case keybase1.TeamInviteCategory_EMAIL:
			// nothing to verify, need to trust the server
		case keybase1.TeamInviteCategory_KEYBASE:
			if err := assertCanAcceptKeybaseInvite(ctx, g, untrustedInviteeFromGregor, invite); err != nil {
				return err
			}
		default:
			return fmt.Errorf("no verification implemented for invite category %s (%+v)", category, invite)
		}

		g.Log.CDebugf(ctx, "checks passed, proceeding with team.ChangeMembership, req = %+v", req)

		if err = team.ChangeMembership(ctx, req); err != nil {
			return err
		}

		// Send chat welcome message
		g.Log.CDebugf(ctx, "sending welcome message for successful SBS handle")
		SendChatInviteWelcomeMessage(ctx, g, team.Name().String(), category, invite.Inviter.Uid,
			untrustedInviteeFromGregor.Uid)

		return nil
	})
}

func assertCanAcceptKeybaseInvite(ctx context.Context, g *libkb.GlobalContext, untrustedInviteeFromGregor keybase1.TeamInvitee, chainInvite keybase1.TeamInvite) error {
	chainUV, err := chainInvite.KeybaseUserVersion()
	if err != nil {
		return err
	}
	if chainUV.Uid.NotEqual(untrustedInviteeFromGregor.Uid) {
		return fmt.Errorf("chain keybase invite link uid %s does not match uid %s in team.sbs message", chainUV.Uid, untrustedInviteeFromGregor.Uid)
	}

	if chainUV.EldestSeqno.Eq(untrustedInviteeFromGregor.EldestSeqno) {
		return nil
	}

	if chainUV.EldestSeqno == 0 {
		g.Log.CDebugf(ctx, "team.sbs invitee eldest seqno: %d, allowing it to take the invite for eldest seqno 0 (reset account)", untrustedInviteeFromGregor.EldestSeqno)
		return nil
	}

	return fmt.Errorf("chain keybase invite link eldest seqno %d does not match eldest seqno %d in team.sbs message", chainUV.EldestSeqno, untrustedInviteeFromGregor.EldestSeqno)
}

func HandleOpenTeamAccessRequest(ctx context.Context, g *libkb.GlobalContext, msg keybase1.TeamOpenReqMsg) (err error) {
	ctx = libkb.WithLogTag(ctx, "CLKR")
	defer g.CTrace(ctx, "HandleOpenTeamAccessRequest", func() error { return err })()

	return RetryOnSigOldSeqnoError(ctx, g, func(ctx context.Context, _ int) error {
		team, err := Load(ctx, g, keybase1.LoadTeamArg{
			ID:          msg.TeamID,
			Public:      msg.TeamID.IsPublic(),
			ForceRepoll: true,
		})
		if err != nil {
			return err
		}

		if !team.IsOpen() {
			g.Log.CDebugf(ctx, "team %q is not an open team", team.Name)
			return nil // Not an error - let the handler dismiss the message.
		}

		var req keybase1.TeamChangeReq
		role := team.chain().inner.OpenTeamJoinAs

		for _, tar := range msg.Tars {
			uv := NewUserVersion(tar.Uid, tar.EldestSeqno)
			currentRole, err := team.MemberRole(ctx, uv)
			if err != nil {
				return err
			}

			if currentRole.IsOrAbove(role) {
				continue
			}

			switch role {
			case keybase1.TeamRole_READER:
				req.Readers = append(req.Readers, uv)
			case keybase1.TeamRole_WRITER:
				req.Writers = append(req.Writers, uv)
			default:
				return fmt.Errorf("Unexpected role to add to open team: %v", role)
			}
		}

		return team.ChangeMembership(ctx, req)
	})
}

func HandleTeamSeitan(ctx context.Context, g *libkb.GlobalContext, msg keybase1.TeamSeitanMsg) (err error) {
	ctx = libkb.WithLogTag(ctx, "CLKR")
	defer g.CTrace(ctx, "HandleTeamSeitan", func() error { return err })()

	team, err := Load(ctx, g, keybase1.LoadTeamArg{
		ID:          msg.TeamID,
		Public:      msg.TeamID.IsPublic(),
		ForceRepoll: true,
	})
	if err != nil {
		return err
	}

	var req keybase1.TeamChangeReq
	req.CompletedInvites = make(map[keybase1.TeamInviteID]keybase1.UserVersionPercentForm)

	for _, seitan := range msg.Seitans {
		invite, found := team.chain().FindActiveInviteByID(seitan.InviteID)
		if !found {
			return libkb.NotFoundError{}
		}

		peikey, err := SeitanDecodePEIKey(string(invite.Name))
		if err != nil {
			return err
		}

		ikey, err := peikey.DecryptIKey(ctx, team)
		if err != nil {
			return err
		}

		sikey, err := ikey.GenerateSIKey()
		if err != nil {
			return err
		}

		akey, _, err := sikey.GenerateAcceptanceKey(seitan.Uid, seitan.EldestSeqno, seitan.UnixCTime)
		if err != nil {
			return err
		}

		// Decode given AKey to be able to do secure hash comparison.
		decodedAKey, err := base64.StdEncoding.DecodeString(seitan.Akey)
		if err != nil {
			return err
		}

		if !libkb.SecureByteArrayEq(akey, decodedAKey) {
			return fmt.Errorf("did not end up with the same AKey")
		}

		uv := NewUserVersion(seitan.Uid, seitan.EldestSeqno)
		currentRole, err := team.MemberRole(ctx, uv)
		if err != nil {
			return err
		}

		if currentRole.IsOrAbove(invite.Role) {
			continue
		}

		switch invite.Role {
		case keybase1.TeamRole_READER:
			req.Readers = append(req.Readers, uv)
		case keybase1.TeamRole_WRITER:
			req.Writers = append(req.Writers, uv)
		case keybase1.TeamRole_ADMIN:
			req.Readers = append(req.Admins, uv)
		case keybase1.TeamRole_OWNER:
			req.Writers = append(req.Owners, uv)
		default:
			return fmt.Errorf("Unexpected role in invitation: %v", invite.Role)
		}

		req.CompletedInvites[seitan.InviteID] = uv.PercentForm()
	}

	return team.ChangeMembership(ctx, req)
}
<|endoftext|>


<commit_before>// Copyright 2015 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package verify

import (
	"errors"

	"github.com/jacobsa/comeback/blob"
	"github.com/jacobsa/comeback/graph"
	"golang.org/x/net/context"
)

const (
	filePrefix = "f:"
	dirPrefix  = "d:"
)

// Create a visitor for the DAG of blobs in the supplied bucket. Node names are
// expected to be generated by FormatNodeName.
//
// The visitor reads directory blobs, verifies their score, parses them, and
// emits their children as adjacent nodes. For file nodes, the visitor verifies
// that their score exists (according to allScores), and reads and verifies
// their score then ensures they can be decrypted if readFiles is true.
func NewVisitor(
	readFiles bool,
	allScores []blob.Score,
	bs blob.Store) (v graph.Visitor) {
	v = &visitor{}
	return
}

// Create a node name that can be consumed by the visitor and by ParseNodeName.
// If dir is false, the node represents a file.
func FormatNodeName(
	dir bool,
	score blob.Score) (node string) {
	if dir {
		node = dirPrefix + score.Hex()
	} else {
		node = filePrefix + score.Hex()
	}

	return
}

// Parse a node name created by FormatNodeName.
func ParseNodeName(
	node string) (dir bool, score blob.Score, err error) {
	panic("TODO")
}

////////////////////////////////////////////////////////////////////////
// Implementation
////////////////////////////////////////////////////////////////////////

type visitor struct {
}

func (v *visitor) Visit(
	ctx context.Context,
	node string) (adjacent []string, err error) {
	err = errors.New("TODO: Visit")
	return
}
<commit_msg>ParseNodeName<commit_after>// Copyright 2015 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package verify

import (
	"errors"
	"fmt"
	"strings"

	"github.com/jacobsa/comeback/blob"
	"github.com/jacobsa/comeback/graph"
	"golang.org/x/net/context"
)

const (
	filePrefix = "f:"
	dirPrefix  = "d:"
)

// Create a visitor for the DAG of blobs in the supplied bucket. Node names are
// expected to be generated by FormatNodeName.
//
// The visitor reads directory blobs, verifies their score, parses them, and
// emits their children as adjacent nodes. For file nodes, the visitor verifies
// that their score exists (according to allScores), and reads and verifies
// their score then ensures they can be decrypted if readFiles is true.
func NewVisitor(
	readFiles bool,
	allScores []blob.Score,
	bs blob.Store) (v graph.Visitor) {
	v = &visitor{}
	return
}

// Create a node name that can be consumed by the visitor and by ParseNodeName.
// If dir is false, the node represents a file.
func FormatNodeName(
	dir bool,
	score blob.Score) (node string) {
	if dir {
		node = dirPrefix + score.Hex()
	} else {
		node = filePrefix + score.Hex()
	}

	return
}

// Parse a node name created by FormatNodeName.
func ParseNodeName(
	node string) (dir bool, score blob.Score, err error) {
	var hexScore string

	switch {
	case strings.HasPrefix(node, filePrefix):
		hexScore = strings.TrimPrefix(node, filePrefix)

	case strings.HasPrefix(node, dirPrefix):
		dir = true
		hexScore = strings.TrimPrefix(node, dirPrefix)

	default:
		err = fmt.Errorf("Unknown prefix for node name %q", node)
		return
	}

	score, err = blob.ParseHexScore(hexScore)
	if err != nil {
		err = fmt.Errorf("ParseHexScore for node %q: %v", node, err)
		return
	}

	return
}

////////////////////////////////////////////////////////////////////////
// Implementation
////////////////////////////////////////////////////////////////////////

type visitor struct {
}

func (v *visitor) Visit(
	ctx context.Context,
	node string) (adjacent []string, err error) {
	err = errors.New("TODO: Visit")
	return
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"os"
	"text/tabwriter"
)

var cmdLogDrains = &Command{
	Run:      runLogDrains,
	Usage:    "log-drains",
	NeedsApp: true,
	Category: "app",
	Short:    "list log drains" + extra,
	Long: `
Lists log drains on an app.

Example:

    $ hk log-drains
    7f89b6bb-08af-4343-b0b4-d0415dd81712  d.b5f66703-6fb2-4195-a4b1-3ab2f1e3423f  syslog://my.log.host
    23fcdb8a-3095-46f5-abc2-c5f293c54cf1  d.3f17356d-3b5d-4de2-a6aa-cc367e4a8fc8  syslog://my.other.log.host
`,
}

func runLogDrains(cmd *Command, args []string) {
	drains, err := client.LogDrainList(mustApp(), nil)
	must(err)

	w := tabwriter.NewWriter(os.Stdout, 1, 2, 2, ' ', 0)
	defer w.Flush()

	for _, drain := range drains {
		listRec(w, drain.Id, drain.Token, drain.URL)
	}
}

var cmdLogDrainAdd = &Command{
	Run:      runLogDrainAdd,
	Usage:    "log-drain-add <url>",
	NeedsApp: true,
	Category: "app",
	Short:    "add a log drain" + extra,
	Long: `
Adds a log drain to an app.

Example:

    $ hk log-drain-add syslog://my.log.host
    Added log drain to myapp.
`,
}

func runLogDrainAdd(cmd *Command, args []string) {
	if len(args) != 1 {
		cmd.printUsage()
		os.Exit(2)
	}

	url := args[0]
	_, err := client.LogDrainCreate(mustApp(), url)
	must(err)
	log.Printf("Added log drain to %s.", mustApp())
}

var cmdLogDrainRemove = &Command{
	Run:      runLogDrainRemove,
	Usage:    "log-drain-remove <id or url>",
	NeedsApp: true,
	Category: "app",
	Short:    "remove a log drain" + extra,
	Long: `
Removes a log drain from an app.

Example:

    $ hk log-drain-remove 7f89b6bb-08af-4343-b0b4-d0415dd81712
    Removed log drain from myapp.

    $ hk log-drain-remove syslog://my.log.host
    Removed log drain from myapp.
`,
}

func runLogDrainRemove(cmd *Command, args []string) {
	if len(args) != 1 {
		cmd.printUsage()
		os.Exit(2)
	}

	drainId := args[0]
	must(client.LogDrainDelete(mustApp(), drainId))
	log.Printf("Removed log drain from %s.", mustApp())
}
<commit_msg>show addons instead of URLs for those drains<commit_after>package main

import (
	"fmt"
	"log"
	"os"
	"text/tabwriter"
)

var cmdLogDrains = &Command{
	Run:      runLogDrains,
	Usage:    "log-drains",
	NeedsApp: true,
	Category: "app",
	Short:    "list log drains" + extra,
	Long: `
Lists log drains on an app. Shows the drain's ID, as well as its
Add-on name (if it's from an Add-on) or its URL.

Example:

    $ hk log-drains
    6af8b744-c513-4217-9f7c-1234567890ab  logging-addon:jumbo
    7f89b6bb-08af-4343-b0b4-d0415dd81712  syslog://my.log.host
    23fcdb8a-3095-46f5-abc2-c5f293c54cf1  syslog://my.other.log.host
`,
}

func runLogDrains(cmd *Command, args []string) {
	if len(args) != 0 {
		cmd.printUsage()
		os.Exit(2)
	}
	appname := mustApp()

	// fetch app's addons concurrently in case we need to resolve addon names
	addonsch := make(chan []heroku.Addon, 1)
	errch := make(chan error, 1)
	go func(appname string) {
		if addons, err := client.AddonList(appname, nil); err != nil {
			errch <- err
		} else {
			addonsch <- addons
		}
	}(appname)

	drains, err := client.LogDrainList(appname, nil)
	must(err)

	hasAddonDrains := false
	merged := make([]*mergedLogDrain, len(drains))
	for i := range drains {
		if !hasAddonDrains && drains[i].Addon != nil {
			hasAddonDrains = true
		}
		merged[i] = &mergedLogDrain{drain: drains[i], hasAddon: drains[i].Addon != nil}
	}

	if hasAddonDrains {
		// resolve addon names, use those instead of URLs
		select {
		case _ = <-errch:
			// couldn't resolve addons, just move on
		case addons := <-addonsch:
			mergeDrainAddonInfo(merged, addons)
		}
	}

	w := tabwriter.NewWriter(os.Stdout, 1, 2, 2, ' ', 0)
	defer w.Flush()

	for _, m := range merged {
		listRec(w, m.drain.Id, m.addonNameOrURL())
	}
}

type mergedLogDrain struct {
	drain    heroku.LogDrain
	hasAddon bool
	addon    *heroku.Addon
}

func (m *mergedLogDrain) addonNameOrURL() string {
	switch {
	case m.hasAddon && m.addon != nil:
		return m.addon.Plan.Name
	case m.hasAddon:
		return "unknown"
	default:
		return m.drain.URL
	}
}

// merge addon info into log drains
func mergeDrainAddonInfo(merged []*mergedLogDrain, addons []heroku.Addon) {
	for i := range merged {
		if merged[i].hasAddon {
			for j := range addons {
				if merged[i].drain.Addon.Id == addons[j].Id {
					merged[i].addon = &addons[j]
					break
				}
			}
		}
	}
}

var cmdLogDrainAdd = &Command{
	Run:      runLogDrainAdd,
	Usage:    "log-drain-add <url>",
	NeedsApp: true,
	Category: "app",
	Short:    "add a log drain" + extra,
	Long: `
Adds a log drain to an app.

Example:

    $ hk log-drain-add syslog://my.log.host
    Added log drain to myapp.
`,
}

func runLogDrainAdd(cmd *Command, args []string) {
	if len(args) != 1 {
		cmd.printUsage()
		os.Exit(2)
	}

	url := args[0]
	_, err := client.LogDrainCreate(mustApp(), url)
	must(err)
	log.Printf("Added log drain to %s.", mustApp())
}

var cmdLogDrainRemove = &Command{
	Run:      runLogDrainRemove,
	Usage:    "log-drain-remove <id or url>",
	NeedsApp: true,
	Category: "app",
	Short:    "remove a log drain" + extra,
	Long: `
Removes a log drain from an app.

Example:

    $ hk log-drain-remove 7f89b6bb-08af-4343-b0b4-d0415dd81712
    Removed log drain from myapp.

    $ hk log-drain-remove syslog://my.log.host
    Removed log drain from myapp.
`,
}

func runLogDrainRemove(cmd *Command, args []string) {
	if len(args) != 1 {
		cmd.printUsage()
		os.Exit(2)
	}

	drainId := args[0]
	must(client.LogDrainDelete(mustApp(), drainId))
	log.Printf("Removed log drain from %s.", mustApp())
}
<|endoftext|>


<commit_before>// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package sensors

import (
	"errors"
	"sync"
)

var (
	muAStarted sync.Mutex
	aStarted   = false
)

type AccelerometerEvent struct {
	DeltaX    float64
	DeltaY    float64
	DeltaZ    float64
	Timestamp int64
}

// StartAccelerometer starts the accelerometer.
// Once the accelerometer is no longer in use, it should be stopped
// by calling StopAccelerometer.
func StartAccelerometer(samplesPerSec int) error {
	muAStarted.Lock()
	defer muAStarted.Unlock()
	if aStarted {
		return errors.New("sensors: accelerometer already started")
	}
	if err := startAccelerometer(samplesPerSec); err != nil {
		return err
	}
	aStarted = true
	return nil
}

// PollAccelerometer polls n new events from the accelerometer event queue.
// It will block until n events are available to the sensor event queue.
// A call to StartAccelerometer is mandatory to start the accelerometer
// sensor and initialize its event queue.
// You have to call this function from the same OS thread that the
// accelerometer has been started. Use runtime.LockOSThread to lock the
// current goroutine to a particular OS thread.
func PollAccelerometer(n int) []AccelerometerEvent {
	return pollAccelerometer(n)
}

// StopAccelerometer stops the accelerometer and frees the related resources.
func StopAccelerometer() error {
	muAStarted.Lock()
	defer muAStarted.Unlock()
	if !aStarted {
		return errors.New("sensors: accelerometer hasn't been started")
	}
	if err := stopAccelerometer(); err != nil {
		return err
	}
	aStarted = false
	return nil
}

func StartGyroscope(samplesPerSec int) error {
	panic("not yet implemented")
}

func PollGyroscope() (roll, pitch, yaw float64) {
	panic("not yet implemented")
}

func StopGyroscope() error {
	panic("not yet implemented")
}

func StartMagnetometer(samplesPerSec int) error {
	panic("not yet implemented")
}

func PollMagnetometer(azimut, pitch, roll float64) {
	panic("not yet implemented")
}

func StopMagnetometer() error {
	panic("not yet implemented")
}

// Type of the network that is currently in use.
const (
	TypeWiFi = iota
	TypeMobile
	TypeOther
)

// Connectivity status.
const (
	StatusUnknown = iota
	StatusConnecting
	StatusConnected
	StatusDisconnecting
)

// Connectivity returns the type and the status of the network that is
// currently in use.
func Connectivity() (networkType int, status int) {
	panic("not yet implemented")
}
<commit_msg>add gyroscope and magnetometer events.<commit_after>// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package sensors

import (
	"errors"
	"sync"
)

var (
	muAStarted sync.Mutex
	aStarted   = false
)

type AccelerometerEvent struct {
	DeltaX    float64
	DeltaY    float64
	DeltaZ    float64
	Timestamp int64
}

type GyroscopeEvent struct {
	Roll      float64
	Pitch     float64
	Yaw       float64
	Timestamp int64
}

type MagnetometerEvent struct {
	Azimut    float64
	Pitch     float64
	Roll      float64
	Timestamp int64
}

// StartAccelerometer starts the accelerometer.
// Once the accelerometer is no longer in use, it should be stopped
// by calling StopAccelerometer.
func StartAccelerometer(samplesPerSec int) error {
	muAStarted.Lock()
	defer muAStarted.Unlock()
	if aStarted {
		return errors.New("sensors: accelerometer already started")
	}
	if err := startAccelerometer(samplesPerSec); err != nil {
		return err
	}
	aStarted = true
	return nil
}

// PollAccelerometer polls n new events from the accelerometer event queue.
// It will block until n events are available to the sensor event queue.
// A call to StartAccelerometer is mandatory to start the accelerometer
// sensor and initialize its event queue.
// You have to call this function from the same OS thread that the
// accelerometer has been started. Use runtime.LockOSThread to lock the
// current goroutine to a particular OS thread.
func PollAccelerometer(n int) []AccelerometerEvent {
	return pollAccelerometer(n)
}

// StopAccelerometer stops the accelerometer and frees the related resources.
func StopAccelerometer() error {
	muAStarted.Lock()
	defer muAStarted.Unlock()
	if !aStarted {
		return errors.New("sensors: accelerometer hasn't been started")
	}
	if err := stopAccelerometer(); err != nil {
		return err
	}
	aStarted = false
	return nil
}

func StartGyroscope(samplesPerSec int) error {
	panic("not yet implemented")
}

func PollGyroscope(n int) (roll, pitch, yaw float64) {
	panic("not yet implemented")
}

func StopGyroscope() error {
	panic("not yet implemented")
}

func StartMagnetometer(samplesPerSec int) error {
	panic("not yet implemented")
}

func PollMagnetometer(azimut, pitch, roll float64) {
	panic("not yet implemented")
}

func StopMagnetometer() error {
	panic("not yet implemented")
}

// Type of the network that is currently in use.
const (
	TypeWiFi = iota
	TypeMobile
	TypeOther
)

// Connectivity status.
const (
	StatusUnknown = iota
	StatusConnecting
	StatusConnected
	StatusDisconnecting
)

// Connectivity returns the type and the status of the network that is
// currently in use.
func Connectivity() (networkType int, status int) {
	panic("not yet implemented")
}
<|endoftext|>