<commit_before>package gerrittest

import (
	"context"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"sync"
	"time"

	"github.com/opalmer/gerrittest/internal"
)

var (
	// DefaultGitCommands contains a mapping of git commands
	// to their arguments. This is used by Repository for running
	// git commands.
	DefaultGitCommands = map[string][]string{
		"status": {"status", "--porcelain"},
		"init":   {"init", "--quiet"},
		"config": {"config", "--local"},
		"add":    {"add", "--force"},
	}

	// DefaultCommitHookName is the name of the hook installed by
	// InstallCommitHook.
	DefaultCommitHookName = "commit-msg"

	// ErrRepositoryNotInitialized is returned any function that needs an
	// initialized repository.
	ErrRepositoryNotInitialized = errors.New("Repository not initialized")
)

// RepositoryConfig is used to store information about a repository.
type RepositoryConfig struct {
	// Path is the path to the repository on disk.
	Path string

	// Command is the git command to run. Defaults to 'git'
	Command string

	// Ctx is the context to use when running commands. Defaults to
	// context.Background()
	Ctx context.Context

	// CommandTimeout is the amount
	CommandTimeout time.Duration

	// PrivateKey is the path to the private key to use for communicating
	// with the git server. Certain functions may return errors if this value
	// is not set.
	PrivateKey string

	// GitConfig are key:value parts of git configuration options
	// to set. Defaults to:
	// {
	//   "user.name":       "admin",
	//   "user.email":      "admin@localhost",
	//   "core.sshCommand": "ssh -i {PrivateKey} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no",
	// }
	GitConfig map[string]string
}

// Repository is used to store information about an interact
// with a git repository.
type Repository struct {
	mtx  *sync.Mutex
	cfg  *RepositoryConfig
	init bool
	Path string            `json:"path"`
}

// setEnvironment sets up the environment for the given command.
func (r *Repository) setEnvironment(cmd *exec.Cmd) error {
	// We'll need the user to set $HOME otherwise some git commands won't work.
	usr, err := user.Current()
	if err != nil {
		return err
	}
	cmd.Env = append(cmd.Env, fmt.Sprintf("HOME=%s", usr.HomeDir))

	// Set environment variables to ensure the proper ssh command is run. Not
	// all versions of git support core.sshCommand from the config.
	for _, key := range []string{"GIT_SSH_COMMAND", "GIT_SSH"} {
		cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, r.cfg.GitConfig["core.sshCommand"]))
	}
	return nil
}

func (r *Repository) run(cmd *exec.Cmd) (string, string, error) {
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return "", "", err
	}
	stderr, err := cmd.StderrPipe()
	if err != nil {
		return "", "", err
	}
	if err := cmd.Start(); err != nil {
		return "", "", err
	}
	bytesOut, err := ioutil.ReadAll(stdout)
	if err != nil {
		return "", "", err
	}
	bytesErr, err := ioutil.ReadAll(stderr)
	if err != nil {
		return string(bytesOut), "", err
	}
	return string(bytesOut), string(bytesErr), cmd.Wait()
}

// Git runs git with the provided arguments. This also ensures the proper
// working path and environment are set before calling git.
func (r *Repository) Git(args []string) (string, string, error) {
	workdir, err := os.Getwd()
	if err != nil {
		return "", "", err
	}

	// Change directories to the directory of the repository. Technically
	// there's a -C flag but not all versions of git have this flag and not
	// all subcommands respect it the same way.
	defer os.Chdir(workdir) // nolint: errcheck
	if err := os.Chdir(r.Path); err != nil {
		return "", "", err
	}

	ctx, cancel := context.WithTimeout(r.cfg.Ctx, r.cfg.CommandTimeout)
	defer cancel()
	cmd := exec.CommandContext(ctx, r.cfg.Command, args...)
	if err := r.setEnvironment(cmd); err != nil {
		return "", "", err
	}
	return r.run(cmd)
}

// Status returns the current status of the repository.
func (r *Repository) Status() (string, error) {
	stdout, _, err := r.Git(DefaultGitCommands["status"])
	return stdout, err
}

// Init calls 'git init' on the repository but only if it does not appear
// to already be a repository.
func (r *Repository) Init() error {
	r.mtx.Lock()
	defer r.mtx.Unlock()
	if r.init {
		return nil
	}
	if _, err := r.Status(); err != nil {
		if _, _, err := r.Git(DefaultGitCommands["init"]); err != nil {
			return err
		}
	}
	r.init = true
	return nil
}

// InstallCommitHook copies the commit hook into the hooks directory of the
// repository. If the repository has not been initialized yet an error will
// be returned. Note, this function will overwrite the existing commit-msg hook
// by default.
func (r *Repository) InstallCommitHook() error {
	r.mtx.Lock()
	defer r.mtx.Unlock()
	if !r.init {
		return ErrRepositoryNotInitialized
	}
	return ioutil.WriteFile(
		filepath.Join(r.Path, ".git", "hooks", DefaultCommitHookName),
		internal.MustAsset("internal/commit-msg"), 0700)
}

// Config will call `git config --local key value`.
func (r *Repository) Config(key string, value string) error {
	r.mtx.Lock()
	defer r.mtx.Unlock()
	if !r.init {
		return ErrRepositoryNotInitialized
	}
	_, _, err := r.Git(append(DefaultGitCommands["config"], key, value))
	return err
}

// SetConfiguration will iterate over the configuration keys provided
// by the RepositoryConfig struct and call Config() on each.
func (r *Repository) SetConfiguration() error {
	for key, value := range r.cfg.GitConfig {
		if err := r.Config(key, value); err != nil {
			return err
		}
	}
	return nil
}

// Add adds a path to the repository. The path must be relative to the root of
// the repository.
func (r *Repository) Add(paths ...string) error {
	r.mtx.Lock()
	defer r.mtx.Unlock()
	if !r.init {
		return ErrRepositoryNotInitialized
	}
	_, _, err := r.Git(append(DefaultGitCommands["add"], paths...))
	return err
}

// Commit will add a new commit to the repository with the
// given message.
func (r *Repository) Commit(message string) error {
	r.mtx.Lock()
	defer r.mtx.Unlock()
	if !r.init {
		return ErrRepositoryNotInitialized
	}
	return nil
}

// Push will push changes to the given remote and reference. `remote` will
// default to 'origin' if not provided and `ref` will default to
// 'HEAD:refs/for/master' if not provided.
func (r *Repository) Push(remote string, ref string) error {
	r.mtx.Lock()
	defer r.mtx.Unlock()
	if !r.init {
		return ErrRepositoryNotInitialized
	}
	return nil
}

// CreateFromContainer adds a new remote based on the provided spec.
// nolint: unused,gosimple,unconvert,varcheck
func (r *Repository) CreateFromContainer(container *Container, remoteName string, project string) error {
	r.mtx.Lock()
	defer r.mtx.Unlock()
	if !r.init {
		return ErrRepositoryNotInitialized
	}
	return nil
}

// Remove will remove the entire repository from disk, useful for temporary
// repositories. This cannot be reversed.
func (r *Repository) Remove() error {
	return os.RemoveAll(r.Path)
}

// NewRepository constructs and returns a *Repository struct. It will also
// ensure the repository is properly setup before returning.
func NewRepository(cfg *RepositoryConfig) (*Repository, error) {
	repo := &Repository{
		mtx: &sync.Mutex{}, init: false,
		Path: cfg.Path, cfg: cfg}
	if err := repo.Init(); err != nil {
		return nil, err
	}
	if err := repo.InstallCommitHook(); err != nil {
		return nil, err
	}
	if err := repo.SetConfiguration(); err != nil {
		return nil, err
	}

	return repo, nil
}

// newRepositoryConfig returns a *RepositoryConfig struct. If no path is
// provided then one will be generated for you.
func newRepositoryConfig(path string, privateKey string) (*RepositoryConfig, error) {
	if privateKey == "" {
		return nil, errors.New("Missing private key")
	}

	if path == "" {
		tmppath, err := ioutil.TempDir("", "gerritest-")
		if err != nil {
			return nil, err
		}
		path = tmppath
	}

	if err := os.MkdirAll(path, 0700); err != nil {
		return nil, err
	}

	return &RepositoryConfig{
		Path:           path,
		Ctx:            context.Background(),
		Command:        "git",
		CommandTimeout: time.Minute * 10,
		PrivateKey:     privateKey,
		GitConfig: map[string]string{
			"user.name":       "admin",
			"user.email":      "admin@localhost",
			"core.sshCommand": fmt.Sprintf("ssh -i %s -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no", privateKey),
		},
	}, nil
}
<commit_msg>fmt fix<commit_after>package gerrittest

import (
	"context"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"sync"
	"time"

	"github.com/opalmer/gerrittest/internal"
)

var (
	// DefaultGitCommands contains a mapping of git commands
	// to their arguments. This is used by Repository for running
	// git commands.
	DefaultGitCommands = map[string][]string{
		"status": {"status", "--porcelain"},
		"init":   {"init", "--quiet"},
		"config": {"config", "--local"},
		"add":    {"add", "--force"},
	}

	// DefaultCommitHookName is the name of the hook installed by
	// InstallCommitHook.
	DefaultCommitHookName = "commit-msg"

	// ErrRepositoryNotInitialized is returned any function that needs an
	// initialized repository.
	ErrRepositoryNotInitialized = errors.New("Repository not initialized")
)

// RepositoryConfig is used to store information about a repository.
type RepositoryConfig struct {
	// Path is the path to the repository on disk.
	Path string

	// Command is the git command to run. Defaults to 'git'
	Command string

	// Ctx is the context to use when running commands. Defaults to
	// context.Background()
	Ctx context.Context

	// CommandTimeout is the amount
	CommandTimeout time.Duration

	// PrivateKey is the path to the private key to use for communicating
	// with the git server. Certain functions may return errors if this value
	// is not set.
	PrivateKey string

	// GitConfig are key:value parts of git configuration options
	// to set. Defaults to:
	// {
	//   "user.name":       "admin",
	//   "user.email":      "admin@localhost",
	//   "core.sshCommand": "ssh -i {PrivateKey} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no",
	// }
	GitConfig map[string]string
}

// Repository is used to store information about an interact
// with a git repository.
type Repository struct {
	mtx  *sync.Mutex
	cfg  *RepositoryConfig
	init bool
	Path string `json:"path"`
}

// setEnvironment sets up the environment for the given command.
func (r *Repository) setEnvironment(cmd *exec.Cmd) error {
	// We'll need the user to set $HOME otherwise some git commands won't work.
	usr, err := user.Current()
	if err != nil {
		return err
	}
	cmd.Env = append(cmd.Env, fmt.Sprintf("HOME=%s", usr.HomeDir))

	// Set environment variables to ensure the proper ssh command is run. Not
	// all versions of git support core.sshCommand from the config.
	for _, key := range []string{"GIT_SSH_COMMAND", "GIT_SSH"} {
		cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, r.cfg.GitConfig["core.sshCommand"]))
	}
	return nil
}

func (r *Repository) run(cmd *exec.Cmd) (string, string, error) {
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return "", "", err
	}
	stderr, err := cmd.StderrPipe()
	if err != nil {
		return "", "", err
	}
	if err := cmd.Start(); err != nil {
		return "", "", err
	}
	bytesOut, err := ioutil.ReadAll(stdout)
	if err != nil {
		return "", "", err
	}
	bytesErr, err := ioutil.ReadAll(stderr)
	if err != nil {
		return string(bytesOut), "", err
	}
	return string(bytesOut), string(bytesErr), cmd.Wait()
}

// Git runs git with the provided arguments. This also ensures the proper
// working path and environment are set before calling git.
func (r *Repository) Git(args []string) (string, string, error) {
	workdir, err := os.Getwd()
	if err != nil {
		return "", "", err
	}

	// Change directories to the directory of the repository. Technically
	// there's a -C flag but not all versions of git have this flag and not
	// all subcommands respect it the same way.
	defer os.Chdir(workdir) // nolint: errcheck
	if err := os.Chdir(r.Path); err != nil {
		return "", "", err
	}

	ctx, cancel := context.WithTimeout(r.cfg.Ctx, r.cfg.CommandTimeout)
	defer cancel()
	cmd := exec.CommandContext(ctx, r.cfg.Command, args...)
	if err := r.setEnvironment(cmd); err != nil {
		return "", "", err
	}
	return r.run(cmd)
}

// Status returns the current status of the repository.
func (r *Repository) Status() (string, error) {
	stdout, _, err := r.Git(DefaultGitCommands["status"])
	return stdout, err
}

// Init calls 'git init' on the repository but only if it does not appear
// to already be a repository.
func (r *Repository) Init() error {
	r.mtx.Lock()
	defer r.mtx.Unlock()
	if r.init {
		return nil
	}
	if _, err := r.Status(); err != nil {
		if _, _, err := r.Git(DefaultGitCommands["init"]); err != nil {
			return err
		}
	}
	r.init = true
	return nil
}

// InstallCommitHook copies the commit hook into the hooks directory of the
// repository. If the repository has not been initialized yet an error will
// be returned. Note, this function will overwrite the existing commit-msg hook
// by default.
func (r *Repository) InstallCommitHook() error {
	r.mtx.Lock()
	defer r.mtx.Unlock()
	if !r.init {
		return ErrRepositoryNotInitialized
	}
	return ioutil.WriteFile(
		filepath.Join(r.Path, ".git", "hooks", DefaultCommitHookName),
		internal.MustAsset("internal/commit-msg"), 0700)
}

// Config will call `git config --local key value`.
func (r *Repository) Config(key string, value string) error {
	r.mtx.Lock()
	defer r.mtx.Unlock()
	if !r.init {
		return ErrRepositoryNotInitialized
	}
	_, _, err := r.Git(append(DefaultGitCommands["config"], key, value))
	return err
}

// SetConfiguration will iterate over the configuration keys provided
// by the RepositoryConfig struct and call Config() on each.
func (r *Repository) SetConfiguration() error {
	for key, value := range r.cfg.GitConfig {
		if err := r.Config(key, value); err != nil {
			return err
		}
	}
	return nil
}

// Add adds a path to the repository. The path must be relative to the root of
// the repository.
func (r *Repository) Add(paths ...string) error {
	r.mtx.Lock()
	defer r.mtx.Unlock()
	if !r.init {
		return ErrRepositoryNotInitialized
	}
	_, _, err := r.Git(append(DefaultGitCommands["add"], paths...))
	return err
}

// Commit will add a new commit to the repository with the
// given message.
func (r *Repository) Commit(message string) error {
	r.mtx.Lock()
	defer r.mtx.Unlock()
	if !r.init {
		return ErrRepositoryNotInitialized
	}
	return nil
}

// Push will push changes to the given remote and reference. `remote` will
// default to 'origin' if not provided and `ref` will default to
// 'HEAD:refs/for/master' if not provided.
func (r *Repository) Push(remote string, ref string) error {
	r.mtx.Lock()
	defer r.mtx.Unlock()
	if !r.init {
		return ErrRepositoryNotInitialized
	}
	return nil
}

// CreateFromContainer adds a new remote based on the provided spec.
// nolint: unused,gosimple,unconvert,varcheck
func (r *Repository) CreateFromContainer(container *Container, remoteName string, project string) error {
	r.mtx.Lock()
	defer r.mtx.Unlock()
	if !r.init {
		return ErrRepositoryNotInitialized
	}
	return nil
}

// Remove will remove the entire repository from disk, useful for temporary
// repositories. This cannot be reversed.
func (r *Repository) Remove() error {
	return os.RemoveAll(r.Path)
}

// NewRepository constructs and returns a *Repository struct. It will also
// ensure the repository is properly setup before returning.
func NewRepository(cfg *RepositoryConfig) (*Repository, error) {
	repo := &Repository{
		mtx: &sync.Mutex{}, init: false,
		Path: cfg.Path, cfg: cfg}
	if err := repo.Init(); err != nil {
		return nil, err
	}
	if err := repo.InstallCommitHook(); err != nil {
		return nil, err
	}
	if err := repo.SetConfiguration(); err != nil {
		return nil, err
	}

	return repo, nil
}

// newRepositoryConfig returns a *RepositoryConfig struct. If no path is
// provided then one will be generated for you.
func newRepositoryConfig(path string, privateKey string) (*RepositoryConfig, error) {
	if privateKey == "" {
		return nil, errors.New("Missing private key")
	}

	if path == "" {
		tmppath, err := ioutil.TempDir("", "gerritest-")
		if err != nil {
			return nil, err
		}
		path = tmppath
	}

	if err := os.MkdirAll(path, 0700); err != nil {
		return nil, err
	}

	return &RepositoryConfig{
		Path:           path,
		Ctx:            context.Background(),
		Command:        "git",
		CommandTimeout: time.Minute * 10,
		PrivateKey:     privateKey,
		GitConfig: map[string]string{
			"user.name":       "admin",
			"user.email":      "admin@localhost",
			"core.sshCommand": fmt.Sprintf("ssh -i %s -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no", privateKey),
		},
	}, nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"code.google.com/p/go-uuid/uuid"
	"github.com/codegangsta/negroni"
	"github.com/julienschmidt/httprouter"
	"github.com/libgit2/git2go"
	"github.com/unrolled/render"

	"github.com/wantedly/risu/registry"
	"github.com/wantedly/risu/schema"
)

const (
	DefaultCloneBasePath = "/tmp/risu/repository/"
)

var ren = render.New()

func create(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	defer r.Body.Close()
	var opts schema.BuildCreateOpts
	err := json.NewDecoder(r.Body).Decode(&opts)
	if err != nil {
		log.Fatal(err)
		ren.JSON(w, http.StatusInternalServerError, map[string]string{"status": "internal server error"})
		return
	}

	if opts.SourceBranch == "" {
		opts.SourceBranch = "master"
	}

	if opts.Dockerfile == "" {
		opts.Dockerfile = "Dockerfile"
	}

	currentTime := time.Now()
	build := schema.Build{
		ID:           uuid.NewUUID(),
		SourceRepo:   opts.SourceRepo,
		SourceBranch: opts.SourceBranch,
		Name:         opts.Name,
		Dockerfile:   opts.Dockerfile,
		Status:       "building",
		CreatedAt:    currentTime,
		UpdatedAt:    currentTime,
	}

	reg := registry.NewRegistry("localfs", "")
	err = reg.Set(build)
	if err != nil {
		log.Fatal(err)
		ren.JSON(w, http.StatusInternalServerError, map[string]string{"status": "internal server error"})
		return
	}
	ren.JSON(w, http.StatusCreated, build)

	// git clone
	err = gitClone(build)
	if err != nil {
		log.Fatal(err)
	}
}

func root(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	ren.JSON(w, http.StatusOK, map[string]string{"status": "ok"})
}

func index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	reg := registry.NewRegistry("localfs", "")
	builds, err := reg.List()
	if err != nil {
		ren.JSON(w, http.StatusInternalServerError, map[string]string{"status": "internal server error"})
		return
	}
	ren.JSON(w, http.StatusOK, builds)
}

func show(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	id := ps.ByName("id")
	uuid := uuid.Parse(id)
	reg := registry.NewRegistry("localfs", "")
	build, err := reg.Get(uuid)
	if err != nil {
		ren.JSON(w, http.StatusNotFound, map[string]string{"status": "not found"})
		return
	}
	ren.JSON(w, http.StatusOK, build)
}

// Clone run "git clone <repository_URL>" and "git checkout branch"
func gitClone(build schema.Build) error {
	basePath := DefaultCloneBasePath
	if _, err := os.Stat(basePath); err != nil {
		os.MkdirAll(basePath, 0755)
	}

	// htpps://<token>@github.com/<SourceRepo>.git
	cloneURL := "https://" + os.Getenv("GITHUB_ACCESS_TOKEN") + "@github.com/" + build.SourceRepo + ".git"

	// debug
	fmt.Println(cloneURL)

	clonePath := basePath + build.SourceRepo

	// debug
	fmt.Println(clonePath)

	_, err := git.Clone(cloneURL, clonePath, &git.CloneOptions{CheckoutBranch: build.SourceBranch})
	if err != nil {
		return err
	}
	return nil
}

func setUpServer() *negroni.Negroni {
	router := httprouter.New()
	router.GET("/", root)
	router.GET("/builds", index)
	router.GET("/builds/:id", show)
	router.POST("/builds", create)

	n := negroni.Classic()
	n.UseHandler(router)
	return n
}

func main() {
	if os.Getenv("GITHUB_ACCESS_TOKEN") == "" {
		log.Fatal("Please provide 'GITHUB_ACCESS_TOKEN' through environment")
		os.Exit(1)
	}
	n := setUpServer()
	n.Run(":8080")
}
<commit_msg>change clone path  risu : /var/risu/github.com/wanteldy/risu  json : /etc/risu/<UUID>.json<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"code.google.com/p/go-uuid/uuid"
	"github.com/codegangsta/negroni"
	"github.com/julienschmidt/httprouter"
	"github.com/libgit2/git2go"
	"github.com/unrolled/render"

	"github.com/wantedly/risu/registry"
	"github.com/wantedly/risu/schema"
)

const (
	DefaultCloneBasePath = "/var/risu/src/"
)

var ren = render.New()

func create(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	defer r.Body.Close()
	var opts schema.BuildCreateOpts
	err := json.NewDecoder(r.Body).Decode(&opts)
	if err != nil {
		log.Fatal(err)
		ren.JSON(w, http.StatusInternalServerError, map[string]string{"status": "internal server error"})
		return
	}

	if opts.SourceBranch == "" {
		opts.SourceBranch = "master"
	}

	if opts.Dockerfile == "" {
		opts.Dockerfile = "Dockerfile"
	}

	currentTime := time.Now()
	build := schema.Build{
		ID:           uuid.NewUUID(),
		SourceRepo:   opts.SourceRepo,
		SourceBranch: opts.SourceBranch,
		Name:         opts.Name,
		Dockerfile:   opts.Dockerfile,
		Status:       "building",
		CreatedAt:    currentTime,
		UpdatedAt:    currentTime,
	}

	reg := registry.NewRegistry("localfs", "")
	err = reg.Set(build)
	if err != nil {
		log.Fatal(err)
		ren.JSON(w, http.StatusInternalServerError, map[string]string{"status": "internal server error"})
		return
	}
	ren.JSON(w, http.StatusCreated, build)

	// git clone
	err = gitClone(build)
	if err != nil {
		log.Fatal(err)
	}
}

func root(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	ren.JSON(w, http.StatusOK, map[string]string{"status": "ok"})
}

func index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	reg := registry.NewRegistry("localfs", "")
	builds, err := reg.List()
	if err != nil {
		ren.JSON(w, http.StatusInternalServerError, map[string]string{"status": "internal server error"})
		return
	}
	ren.JSON(w, http.StatusOK, builds)
}

func show(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	id := ps.ByName("id")
	uuid := uuid.Parse(id)
	reg := registry.NewRegistry("localfs", "")
	build, err := reg.Get(uuid)
	if err != nil {
		ren.JSON(w, http.StatusNotFound, map[string]string{"status": "not found"})
		return
	}
	ren.JSON(w, http.StatusOK, build)
}

// Clone run "git clone <repository_URL>" and "git checkout branch"
func gitClone(build schema.Build) error {
	basePath := DefaultCloneBasePath
	if _, err := os.Stat(basePath); err != nil {
		os.MkdirAll(basePath, 0755)
	}

	// htpps://<token>@github.com/<SourceRepo>.git
	cloneURL := "https://" + os.Getenv("GITHUB_ACCESS_TOKEN") + "@github.com/" + build.SourceRepo + ".git"

	// debug
	fmt.Println(cloneURL)

	clonePath := basePath + "github.com/" + build.SourceRepo

	// debug
	fmt.Println(clonePath)

	_, err := git.Clone(cloneURL, clonePath, &git.CloneOptions{CheckoutBranch: build.SourceBranch})
	if err != nil {
		return err
	}
	return nil
}

func setUpServer() *negroni.Negroni {
	router := httprouter.New()
	router.GET("/", root)
	router.GET("/builds", index)
	router.GET("/builds/:id", show)
	router.POST("/builds", create)

	n := negroni.Classic()
	n.UseHandler(router)
	return n
}

func main() {
	if os.Getenv("GITHUB_ACCESS_TOKEN") == "" {
		log.Fatal("Please provide 'GITHUB_ACCESS_TOKEN' through environment")
		os.Exit(1)
	}
	n := setUpServer()
	n.Run(":8080")
}
<|endoftext|>


<commit_before>// (c) Copyright 2015-2017 JONNALAGADDA Srinivas
//
// 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 flow

import (
	"database/sql"
	"errors"
	"fmt"
	"math"
	"strings"
)

// RoleID is the type of unique role identifiers.
type RoleID int64

// Role represents a collection of privileges.
//
// Each group in the system can have one or more roles assigned.
type Role struct {
	ID   RoleID `json:"ID"`   // globally-unique ID of this role
	Name string `json:"Name"` // name of this role
}

// Unexported type, only for convenience methods.
type _Roles struct{}

var _roles *_Roles

func init() {
	_roles = &_Roles{}
}

// Roles provides a resource-like interface to roles in the system.
func Roles() *_Roles {
	return _roles
}

// New creates a role with the given name.
func (rs *_Roles) New(otx *sql.Tx, name string) (RoleID, error) {
	name = strings.TrimSpace(name)
	if name == "" {
		return 0, errors.New("name cannot not be empty")
	}

	var tx *sql.Tx
	if otx == nil {
		tx, err := db.Begin()
		if err != nil {
			return 0, err
		}
		defer tx.Rollback()
	} else {
		tx = otx
	}

	res, err := tx.Exec("INSERT INTO wf_roles_master(name) VALUES(?)", name)
	if err != nil {
		return 0, err
	}
	id, err := res.LastInsertId()
	if err != nil {
		return 0, err
	}

	if otx == nil {
		err = tx.Commit()
		if err != nil {
			return 0, err
		}
	}

	return RoleID(id), nil
}

// List answers a subset of the roles, based on the input
// specification.
//
// Result set begins with ID >= `offset`, and has not more than
// `limit` elements.  A value of `0` for `offset` fetches from the
// beginning, while a value of `0` for `limit` fetches until the end.
func (rs *_Roles) List(offset, limit int64) ([]*Role, error) {
	if offset < 0 || limit < 0 {
		return nil, errors.New("offset and limit must be non-negative integers")
	}
	if limit == 0 {
		limit = math.MaxInt64
	}

	q := `
	SELECT id, name
	FROM wf_roles_master
	ORDER BY id
	LIMIT ? OFFSET ?
	`
	rows, err := db.Query(q, limit, offset)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	ary := make([]*Role, 0, 10)
	for rows.Next() {
		var elem Role
		err = rows.Scan(&elem.ID, &elem.Name)
		if err != nil {
			return nil, err
		}
		ary = append(ary, &elem)
	}
	if err = rows.Err(); err != nil {
		return nil, err
	}

	return ary, nil
}

// Get loads the role object corresponding to the given role ID from
// the database, and answers that.
func (rs *_Roles) Get(id RoleID) (*Role, error) {
	if id <= 0 {
		return nil, errors.New("ID must be a positive integer")
	}

	var elem Role
	row := db.QueryRow("SELECT id, name FROM wf_roles_master WHERE id = ?", id)
	err := row.Scan(&elem.ID, &elem.Name)
	if err != nil {
		return nil, err
	}

	return &elem, nil
}

// GetByName answers the role, if one with the given name is
// registered; `nil` and the error, otherwise.
func (rs *_Roles) GetByName(name string) (*Role, error) {
	name = strings.TrimSpace(name)
	if name == "" {
		return nil, errors.New("role cannot be empty")
	}

	var elem Role
	row := db.QueryRow("SELECT id, name FROM wf_roles_master WHERE name = ?", name)
	err := row.Scan(&elem.ID, &elem.Name)
	if err != nil {
		return nil, err
	}

	return &elem, nil
}

// Rename renames the given role.
func (rs *_Roles) Rename(otx *sql.Tx, id RoleID, name string) error {
	name = strings.TrimSpace(name)
	if name == "" {
		return errors.New("name cannot be empty")
	}

	var tx *sql.Tx
	if otx == nil {
		tx, err := db.Begin()
		if err != nil {
			return err
		}
		defer tx.Rollback()
	} else {
		tx = otx
	}

	_, err := tx.Exec("UPDATE wf_roles_master SET name = ? WHERE id = ?", name, id)
	if err != nil {
		return err
	}

	if otx == nil {
		err = tx.Commit()
		if err != nil {
			return err
		}
	}

	return nil
}

// Delete deletes the given role from the system, if no access context
// is actively using it.
func (rs *_Roles) Delete(otx *sql.Tx, id RoleID) error {
	if id <= 0 {
		return errors.New("role ID must be a positive integer")
	}

	row := db.QueryRow("SELECT COUNT(*) FROM wf_access_contexts WHERE role_id = ?", id)
	var n int64
	err := row.Scan(&n)
	if n > 0 {
		return errors.New("role is being used in at least one access context; cannot delete")
	}

	var tx *sql.Tx
	if otx == nil {
		tx, err := db.Begin()
		if err != nil {
			return err
		}
		defer tx.Rollback()
	} else {
		tx = otx
	}

	_, err = tx.Exec("DELETE FROM wf_role_docactions WHERE role_id = ?", id)
	if err != nil {
		return err
	}
	res, err := tx.Exec("DELETE FROM wf_roles_master WHERE id = ?", id)
	if err != nil {
		return err
	}
	n, err = res.RowsAffected()
	if n != 1 {
		return fmt.Errorf("expected number of affected rows : 1; actual affected : %d", n)
	}

	if otx == nil {
		err = tx.Commit()
		if err != nil {
			return err
		}
	}

	return nil
}

// AddPermission adds the given action to this role, for the given
// document type.
func (rs *_Roles) AddPermission(otx *sql.Tx, rid RoleID, dtype DocTypeID, action DocActionID) error {
	var tx *sql.Tx
	if otx == nil {
		tx, err := db.Begin()
		if err != nil {
			return err
		}
		defer tx.Rollback()
	} else {
		tx = otx
	}

	q := `
	INSERT INTO wf_role_docactions(role_id, doctype_id, docaction_id)
	VALUES(?, ?, ?)
	`
	_, err := tx.Exec(q, rid, dtype, action)
	if err != nil {
		return err
	}

	if otx == nil {
		err := tx.Commit()
		if err != nil {
			return err
		}
	}
	return nil
}

// RemovePermission removes all permissions from this role, for the
// given document type.
func (rs *_Roles) RemovePermission(otx *sql.Tx, rid RoleID, dtype DocTypeID, action DocActionID) error {
	var tx *sql.Tx
	if otx == nil {
		tx, err := db.Begin()
		if err != nil {
			return err
		}
		defer tx.Rollback()
	} else {
		tx = otx
	}

	q := `
	DELETE FROM wf_role_docactions
	WHERE role_id = ?
	AND doctype_id = ?
	AND docaction_id = ?
	`
	_, err := tx.Exec(q, rid, dtype, action)
	if err != nil {
		return err
	}

	if otx == nil {
		err := tx.Commit()
		if err != nil {
			return err
		}
	}
	return nil
}

// Permissions answers the current set of permissions this role has.
// It answers `nil` in case the given document type does not have any
// permissions set in this role.
func (rs *_Roles) Permissions(rid RoleID) (map[string]struct {
	DocTypeID DocTypeID
	Actions   []*DocAction
}, error) {
	q := `
	SELECT dtm.id, dtm.name, dam.id, dam.name
	FROM wf_doctypes_master dtm
	JOIN wf_role_docactions rdas ON dtm.id = rdas.doctype_id
	JOIN wf_docactions_master dam ON dam.id = rdas.docaction_id
	WHERE rdas.role_id = ?
	`
	rows, err := db.Query(q, rid)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	das := make(map[string]struct {
		DocTypeID DocTypeID
		Actions   []*DocAction
	})
	for rows.Next() {
		var dt DocType
		var da DocAction
		err = rows.Scan(&dt.ID, &dt.Name, &da.ID, &da.Name)
		if err != nil {
			return nil, err
		}
		st, ok := das[dt.Name]
		if !ok {
			st.DocTypeID = dt.ID
			st.Actions = make([]*DocAction, 0, 1)
		}
		st.Actions = append(st.Actions, &da)
		das[dt.Name] = st
	}
	if err = rows.Err(); err != nil {
		return nil, err
	}

	return das, nil
}

// HasPermission answers `true` if this role has the queried
// permission for the given document type.
func (rs *_Roles) HasPermission(rid RoleID, dtype DocTypeID, action DocActionID) (bool, error) {
	q := `
	SELECT rdas.id FROM wf_role_docactions rdas
	JOIN wf_doctypes_master dtm ON rdas.doctype_id = dtm.id
	JOIN wf_docactions_master dam ON rdas.docaction_id = dam.id
	WHERE rdas.role_id = ?
	AND dtm.id = ?
	AND dam.id = ?
	ORDER BY rdas.id
	LIMIT 1
	`
	row := db.QueryRow(q, rid, dtype, action)
	var n int64
	err := row.Scan(&n)
	if err != nil {
		switch err {
		case sql.ErrNoRows:
			return false, nil

		default:
			return false, err
		}
	}

	return true, nil
}
<commit_msg>Update SQL query to match renamed access context group roles table name<commit_after>// (c) Copyright 2015-2017 JONNALAGADDA Srinivas
//
// 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 flow

import (
	"database/sql"
	"errors"
	"fmt"
	"math"
	"strings"
)

// RoleID is the type of unique role identifiers.
type RoleID int64

// Role represents a collection of privileges.
//
// Each group in the system can have one or more roles assigned.
type Role struct {
	ID   RoleID `json:"ID"`   // globally-unique ID of this role
	Name string `json:"Name"` // name of this role
}

// Unexported type, only for convenience methods.
type _Roles struct{}

var _roles *_Roles

func init() {
	_roles = &_Roles{}
}

// Roles provides a resource-like interface to roles in the system.
func Roles() *_Roles {
	return _roles
}

// New creates a role with the given name.
func (rs *_Roles) New(otx *sql.Tx, name string) (RoleID, error) {
	name = strings.TrimSpace(name)
	if name == "" {
		return 0, errors.New("name cannot not be empty")
	}

	var tx *sql.Tx
	if otx == nil {
		tx, err := db.Begin()
		if err != nil {
			return 0, err
		}
		defer tx.Rollback()
	} else {
		tx = otx
	}

	res, err := tx.Exec("INSERT INTO wf_roles_master(name) VALUES(?)", name)
	if err != nil {
		return 0, err
	}
	id, err := res.LastInsertId()
	if err != nil {
		return 0, err
	}

	if otx == nil {
		err = tx.Commit()
		if err != nil {
			return 0, err
		}
	}

	return RoleID(id), nil
}

// List answers a subset of the roles, based on the input
// specification.
//
// Result set begins with ID >= `offset`, and has not more than
// `limit` elements.  A value of `0` for `offset` fetches from the
// beginning, while a value of `0` for `limit` fetches until the end.
func (rs *_Roles) List(offset, limit int64) ([]*Role, error) {
	if offset < 0 || limit < 0 {
		return nil, errors.New("offset and limit must be non-negative integers")
	}
	if limit == 0 {
		limit = math.MaxInt64
	}

	q := `
	SELECT id, name
	FROM wf_roles_master
	ORDER BY id
	LIMIT ? OFFSET ?
	`
	rows, err := db.Query(q, limit, offset)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	ary := make([]*Role, 0, 10)
	for rows.Next() {
		var elem Role
		err = rows.Scan(&elem.ID, &elem.Name)
		if err != nil {
			return nil, err
		}
		ary = append(ary, &elem)
	}
	if err = rows.Err(); err != nil {
		return nil, err
	}

	return ary, nil
}

// Get loads the role object corresponding to the given role ID from
// the database, and answers that.
func (rs *_Roles) Get(id RoleID) (*Role, error) {
	if id <= 0 {
		return nil, errors.New("ID must be a positive integer")
	}

	var elem Role
	row := db.QueryRow("SELECT id, name FROM wf_roles_master WHERE id = ?", id)
	err := row.Scan(&elem.ID, &elem.Name)
	if err != nil {
		return nil, err
	}

	return &elem, nil
}

// GetByName answers the role, if one with the given name is
// registered; `nil` and the error, otherwise.
func (rs *_Roles) GetByName(name string) (*Role, error) {
	name = strings.TrimSpace(name)
	if name == "" {
		return nil, errors.New("role cannot be empty")
	}

	var elem Role
	row := db.QueryRow("SELECT id, name FROM wf_roles_master WHERE name = ?", name)
	err := row.Scan(&elem.ID, &elem.Name)
	if err != nil {
		return nil, err
	}

	return &elem, nil
}

// Rename renames the given role.
func (rs *_Roles) Rename(otx *sql.Tx, id RoleID, name string) error {
	name = strings.TrimSpace(name)
	if name == "" {
		return errors.New("name cannot be empty")
	}

	var tx *sql.Tx
	if otx == nil {
		tx, err := db.Begin()
		if err != nil {
			return err
		}
		defer tx.Rollback()
	} else {
		tx = otx
	}

	_, err := tx.Exec("UPDATE wf_roles_master SET name = ? WHERE id = ?", name, id)
	if err != nil {
		return err
	}

	if otx == nil {
		err = tx.Commit()
		if err != nil {
			return err
		}
	}

	return nil
}

// Delete deletes the given role from the system, if no access context
// is actively using it.
func (rs *_Roles) Delete(otx *sql.Tx, id RoleID) error {
	if id <= 0 {
		return errors.New("role ID must be a positive integer")
	}

	row := db.QueryRow("SELECT COUNT(*) FROM wf_ac_group_roles WHERE role_id = ?", id)
	var n int64
	err := row.Scan(&n)
	if n > 0 {
		return errors.New("role is being used in at least one access context; cannot delete")
	}

	var tx *sql.Tx
	if otx == nil {
		tx, err := db.Begin()
		if err != nil {
			return err
		}
		defer tx.Rollback()
	} else {
		tx = otx
	}

	_, err = tx.Exec("DELETE FROM wf_role_docactions WHERE role_id = ?", id)
	if err != nil {
		return err
	}
	res, err := tx.Exec("DELETE FROM wf_roles_master WHERE id = ?", id)
	if err != nil {
		return err
	}
	n, err = res.RowsAffected()
	if n != 1 {
		return fmt.Errorf("expected number of affected rows : 1; actual affected : %d", n)
	}

	if otx == nil {
		err = tx.Commit()
		if err != nil {
			return err
		}
	}

	return nil
}

// AddPermission adds the given action to this role, for the given
// document type.
func (rs *_Roles) AddPermission(otx *sql.Tx, rid RoleID, dtype DocTypeID, action DocActionID) error {
	var tx *sql.Tx
	if otx == nil {
		tx, err := db.Begin()
		if err != nil {
			return err
		}
		defer tx.Rollback()
	} else {
		tx = otx
	}

	q := `
	INSERT INTO wf_role_docactions(role_id, doctype_id, docaction_id)
	VALUES(?, ?, ?)
	`
	_, err := tx.Exec(q, rid, dtype, action)
	if err != nil {
		return err
	}

	if otx == nil {
		err := tx.Commit()
		if err != nil {
			return err
		}
	}
	return nil
}

// RemovePermission removes all permissions from this role, for the
// given document type.
func (rs *_Roles) RemovePermission(otx *sql.Tx, rid RoleID, dtype DocTypeID, action DocActionID) error {
	var tx *sql.Tx
	if otx == nil {
		tx, err := db.Begin()
		if err != nil {
			return err
		}
		defer tx.Rollback()
	} else {
		tx = otx
	}

	q := `
	DELETE FROM wf_role_docactions
	WHERE role_id = ?
	AND doctype_id = ?
	AND docaction_id = ?
	`
	_, err := tx.Exec(q, rid, dtype, action)
	if err != nil {
		return err
	}

	if otx == nil {
		err := tx.Commit()
		if err != nil {
			return err
		}
	}
	return nil
}

// Permissions answers the current set of permissions this role has.
// It answers `nil` in case the given document type does not have any
// permissions set in this role.
func (rs *_Roles) Permissions(rid RoleID) (map[string]struct {
	DocTypeID DocTypeID
	Actions   []*DocAction
}, error) {
	q := `
	SELECT dtm.id, dtm.name, dam.id, dam.name
	FROM wf_doctypes_master dtm
	JOIN wf_role_docactions rdas ON dtm.id = rdas.doctype_id
	JOIN wf_docactions_master dam ON dam.id = rdas.docaction_id
	WHERE rdas.role_id = ?
	`
	rows, err := db.Query(q, rid)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	das := make(map[string]struct {
		DocTypeID DocTypeID
		Actions   []*DocAction
	})
	for rows.Next() {
		var dt DocType
		var da DocAction
		err = rows.Scan(&dt.ID, &dt.Name, &da.ID, &da.Name)
		if err != nil {
			return nil, err
		}
		st, ok := das[dt.Name]
		if !ok {
			st.DocTypeID = dt.ID
			st.Actions = make([]*DocAction, 0, 1)
		}
		st.Actions = append(st.Actions, &da)
		das[dt.Name] = st
	}
	if err = rows.Err(); err != nil {
		return nil, err
	}

	return das, nil
}

// HasPermission answers `true` if this role has the queried
// permission for the given document type.
func (rs *_Roles) HasPermission(rid RoleID, dtype DocTypeID, action DocActionID) (bool, error) {
	q := `
	SELECT rdas.id FROM wf_role_docactions rdas
	JOIN wf_doctypes_master dtm ON rdas.doctype_id = dtm.id
	JOIN wf_docactions_master dam ON rdas.docaction_id = dam.id
	WHERE rdas.role_id = ?
	AND dtm.id = ?
	AND dam.id = ?
	ORDER BY rdas.id
	LIMIT 1
	`
	row := db.QueryRow(q, rid, dtype, action)
	var n int64
	err := row.Scan(&n)
	if err != nil {
		switch err {
		case sql.ErrNoRows:
			return false, nil

		default:
			return false, err
		}
	}

	return true, nil
}
<|endoftext|>


<commit_before>package socket

import "sync"

type Room interface {
	Name() string
	Size() int
	Join(so Socket)
	Leave(so Socket)
	Emitter
}

type room struct {
	sync.RWMutex
	name    string
	sockets map[Socket]struct{}
}

func newRoom(name string) Room {
	return &room{
		name:    name,
		sockets: make(map[Socket]struct{}),
	}
}

func (r *room) Name() string {
	return r.name
}

func (r *room) Join(so Socket) {
	r.Lock()
	defer r.Unlock()
	r.sockets[so] = struct{}{}
}

func (r *room) Leave(so Socket) {
	r.Lock()
	defer r.Unlock()
	delete(r.sockets, so)
}

func (r *room) Size() int {
	r.RLock()
	defer r.RUnlock()
	return len(r.sockets)
}

func (r *room) Emit(event string, args ...interface{}) (err error) {
	r.RLock()
	defer r.RUnlock()
	for so, _ := range r.sockets {
		if er := so.Emit(event, args...); er != nil {
			err = err
		}
	}
	return err
}
<commit_msg>emit to a Room is blocking, but only locks the room and room set to obtain the list of sockets in the room<commit_after>package socket

import "sync"

type Room interface {
	Name() string
	Size() int
	Join(so Socket)
	Leave(so Socket)
	Emitter
}

type room struct {
	sync.RWMutex
	name    string
	sockets map[Socket]struct{}
}

func newRoom(name string) Room {
	return &room{
		name:    name,
		sockets: make(map[Socket]struct{}),
	}
}

func (r *room) Name() string {
	return r.name
}

func (r *room) Join(so Socket) {
	r.Lock()
	defer r.Unlock()
	r.sockets[so] = struct{}{}
}

func (r *room) Leave(so Socket) {
	r.Lock()
	defer r.Unlock()
	delete(r.sockets, so)
}

func (r *room) Size() int {
	r.RLock()
	defer r.RUnlock()
	return len(r.sockets)
}

func (r *room) getSockets() []Socket {
	r.RLock()
	defer r.RUnlock()
	sockets := make([]Socket, len(r.sockets))
	i := 0
	for so, _ := range r.sockets {
		sockets[i] = so
		i++
	}
	return sockets
}

func (r *room) Emit(event string, args ...interface{}) (err error) {
	for _, so := range r.getSockets() {
		if er := so.Emit(event, args...); er != nil {
			err = err
		}
	}
	return err
}
<|endoftext|>


<commit_before>package runc

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"strconv"
	"syscall"
	"time"

	specs "github.com/opencontainers/runtime-spec/specs-go"
)

// Format is the type of log formatting options avaliable
type Format string

const (
	none Format = ""
	JSON Format = "json"
	Text Format = "text"
	// DefaultCommand is the default command for Runc
	DefaultCommand string = "runc"
)

// Runc is the client to the runc cli
type Runc struct {
	//If command is empty, DefaultCommand is used
	Command   string
	Root      string
	Debug     bool
	Log       string
	LogFormat Format
}

// List returns all containers created inside the provided runc root directory
func (r *Runc) List(context context.Context) ([]*Container, error) {
	data, err := r.command(context, "list", "--format=json").Output()
	if err != nil {
		return nil, err
	}
	var out []*Container
	if err := json.Unmarshal(data, &out); err != nil {
		return nil, err
	}
	return out, nil
}

// State returns the state for the container provided by id
func (r *Runc) State(context context.Context, id string) (*Container, error) {
	data, err := r.command(context, "state", id).CombinedOutput()
	if err != nil {
		return nil, fmt.Errorf("%s: %s", err, data)
	}
	var c Container
	if err := json.Unmarshal(data, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

type CreateOpts struct {
	IO
	// PidFile is a path to where a pid file should be created
	PidFile       string
	ConsoleSocket string
	Detach        bool
	NoPivot       bool
	NoNewKeyring  bool
}

type IO struct {
	Stdin  io.Reader
	Stdout io.Writer
	Stderr io.Writer
}

func (i *IO) Close() error {
	var err error
	for _, v := range []interface{}{
		i.Stdin,
		i.Stderr,
		i.Stdout,
	} {
		if v != nil {
			if c, ok := v.(io.Closer); ok {
				if cerr := c.Close(); err == nil {
					err = cerr
				}
			}
		}
	}
	return err
}

func (o IO) setSTDIO(cmd *exec.Cmd) {
	cmd.Stdin = o.Stdin
	cmd.Stdout = o.Stdout
	cmd.Stderr = o.Stderr
}

func (o *CreateOpts) args() (out []string) {
	if o.PidFile != "" {
		out = append(out, "--pid-file", o.PidFile)
	}
	if o.ConsoleSocket != "" {
		out = append(out, "--console-socket", o.ConsoleSocket)
	}
	if o.NoPivot {
		out = append(out, "--no-pivot")
	}
	if o.NoNewKeyring {
		out = append(out, "--no-new-keyring")
	}
	if o.Detach {
		out = append(out, "--detach")
	}
	return out
}

// Create creates a new container and returns its pid if it was created successfully
func (r *Runc) Create(context context.Context, id, bundle string, opts *CreateOpts) error {
	args := []string{"create", "--bundle", bundle}
	if opts != nil {
		args = append(args, opts.args()...)
	}
	cmd := r.command(context, append(args, id)...)
	if opts != nil {
		opts.setSTDIO(cmd)
	}
	return runOrError(cmd)
}

// Start will start an already created container
func (r *Runc) Start(context context.Context, id string) error {
	return runOrError(r.command(context, "start", id))
}

type ExecOpts struct {
	IO
	PidFile       string
	Uid           int
	Gid           int
	Cwd           string
	Tty           bool
	ConsoleSocket string
	Detach        bool
}

func (o *ExecOpts) args() (out []string) {
	out = append(out, "--user", fmt.Sprintf("%d:%d", o.Uid, o.Gid))
	if o.Tty {
		out = append(out, "--tty")
	}
	if o.ConsoleSocket != "" {
		out = append(out, "--console", o.ConsoleSocket)
	}
	if o.Cwd != "" {
		out = append(out, "--cwd", o.Cwd)
	}
	if o.Detach {
		out = append(out, "--detach")
	}
	if o.PidFile != "" {
		out = append(out, "--pid-file", o.PidFile)
	}
	return out
}

// Exec executres and additional process inside the container based on a full
// OCI Process specification
func (r *Runc) Exec(context context.Context, id string, spec specs.Process, opts *ExecOpts) error {
	f, err := ioutil.TempFile("", "-process")
	if err != nil {
		return err
	}
	defer os.Remove(f.Name())
	err = json.NewEncoder(f).Encode(spec)
	f.Close()
	if err != nil {
		return err
	}
	args := []string{"exec", "--process", f.Name()}
	if opts != nil {
		args = append(args, opts.args()...)
	}
	cmd := r.command(context, append(args, id)...)
	if opts != nil {
		opts.setSTDIO(cmd)
	}
	return runOrError(cmd)
}

// Run runs the create, start, delete lifecycle of the container
// and returns its exit status after it has exited
func (r *Runc) Run(context context.Context, id, bundle string, opts *CreateOpts) (int, error) {
	args := []string{"run", "--bundle", bundle}
	if opts != nil {
		args = append(args, opts.args()...)
	}
	cmd := r.command(context, append(args, id)...)
	if opts != nil {
		opts.setSTDIO(cmd)
	}
	if err := cmd.Start(); err != nil {
		return -1, err
	}
	status, err := cmd.Process.Wait()
	if err != nil {
		return -1, err
	}
	return status.Sys().(syscall.WaitStatus).ExitStatus(), nil
}

// Delete deletes the container
func (r *Runc) Delete(context context.Context, id string) error {
	return runOrError(r.command(context, "delete", id))
}

// Kill sends the specified signal to the container
func (r *Runc) Kill(context context.Context, id string, sig int) error {
	return runOrError(r.command(context, "kill", id, strconv.Itoa(sig)))
}

// Stats return the stats for a container like cpu, memory, and io
func (r *Runc) Stats(context context.Context, id string) (*Stats, error) {
	cmd := r.command(context, "events", "--stats", id)
	rd, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}
	defer func() {
		rd.Close()
		cmd.Wait()
	}()
	if err := cmd.Start(); err != nil {
		return nil, err
	}
	var e Event
	if err := json.NewDecoder(rd).Decode(&e); err != nil {
		return nil, err
	}
	return e.Stats, nil
}

// Events returns an event stream from runc for a container with stats and OOM notifications
func (r *Runc) Events(context context.Context, id string, interval time.Duration) (chan *Event, error) {
	cmd := r.command(context, "events", fmt.Sprintf("--interval=%ds", int(interval.Seconds())), id)
	rd, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}
	if err := cmd.Start(); err != nil {
		rd.Close()
		return nil, err
	}
	var (
		dec = json.NewDecoder(rd)
		c   = make(chan *Event, 128)
	)
	go func() {
		defer func() {
			close(c)
			rd.Close()
			cmd.Wait()
		}()
		for {
			var e Event
			if err := dec.Decode(&e); err != nil {
				if err == io.EOF {
					return
				}
				e = Event{
					Type: "error",
					Err:  err,
				}
			}
			c <- &e
		}
	}()
	return c, nil
}

// Pause the container with the provided id
func (r *Runc) Pause(context context.Context, id string) error {
	return runOrError(r.command(context, "pause", id))
}

// Resume the container with the provided id
func (r *Runc) Resume(context context.Context, id string) error {
	return runOrError(r.command(context, "resume", id))
}

// Ps lists all the processes inside the container returning their pids
func (r *Runc) Ps(context context.Context, id string) ([]int, error) {
	data, err := r.command(context, "ps", "--format", "json", id).CombinedOutput()
	if err != nil {
		return nil, fmt.Errorf("%s: %s", err, data)
	}
	var pids []int
	if err := json.Unmarshal(data, &pids); err != nil {
		return nil, err
	}
	return pids, nil
}

func (r *Runc) args() (out []string) {
	if r.Root != "" {
		out = append(out, "--root", r.Root)
	}
	if r.Debug {
		out = append(out, "--debug")
	}
	if r.Log != "" {
		out = append(out, "--log", r.Log)
	}
	if r.LogFormat != none {
		out = append(out, "--log-format", string(r.LogFormat))
	}
	return out
}

func (r *Runc) command(context context.Context, args ...string) *exec.Cmd {
	command := r.Command
	if command == "" {
		command = DefaultCommand
	}
	return exec.CommandContext(context,
		command, append(r.args(), args...)...)
}
<commit_msg>Add pdeathsig and kill --all support<commit_after>package runc

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"strconv"
	"syscall"
	"time"

	specs "github.com/opencontainers/runtime-spec/specs-go"
)

// Format is the type of log formatting options avaliable
type Format string

const (
	none Format = ""
	JSON Format = "json"
	Text Format = "text"
	// DefaultCommand is the default command for Runc
	DefaultCommand string = "runc"
)

// Runc is the client to the runc cli
type Runc struct {
	//If command is empty, DefaultCommand is used
	Command      string
	Root         string
	Debug        bool
	Log          string
	LogFormat    Format
	PdeathSignal syscall.Signal
}

// List returns all containers created inside the provided runc root directory
func (r *Runc) List(context context.Context) ([]*Container, error) {
	data, err := r.command(context, "list", "--format=json").Output()
	if err != nil {
		return nil, err
	}
	var out []*Container
	if err := json.Unmarshal(data, &out); err != nil {
		return nil, err
	}
	return out, nil
}

// State returns the state for the container provided by id
func (r *Runc) State(context context.Context, id string) (*Container, error) {
	data, err := r.command(context, "state", id).CombinedOutput()
	if err != nil {
		return nil, fmt.Errorf("%s: %s", err, data)
	}
	var c Container
	if err := json.Unmarshal(data, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

type CreateOpts struct {
	IO
	// PidFile is a path to where a pid file should be created
	PidFile       string
	ConsoleSocket string
	Detach        bool
	NoPivot       bool
	NoNewKeyring  bool
}

type IO struct {
	Stdin  io.Reader
	Stdout io.Writer
	Stderr io.Writer
}

func (i *IO) Close() error {
	var err error
	for _, v := range []interface{}{
		i.Stdin,
		i.Stderr,
		i.Stdout,
	} {
		if v != nil {
			if c, ok := v.(io.Closer); ok {
				if cerr := c.Close(); err == nil {
					err = cerr
				}
			}
		}
	}
	return err
}

func (o IO) setSTDIO(cmd *exec.Cmd) {
	cmd.Stdin = o.Stdin
	cmd.Stdout = o.Stdout
	cmd.Stderr = o.Stderr
}

func (o *CreateOpts) args() (out []string) {
	if o.PidFile != "" {
		out = append(out, "--pid-file", o.PidFile)
	}
	if o.ConsoleSocket != "" {
		out = append(out, "--console-socket", o.ConsoleSocket)
	}
	if o.NoPivot {
		out = append(out, "--no-pivot")
	}
	if o.NoNewKeyring {
		out = append(out, "--no-new-keyring")
	}
	if o.Detach {
		out = append(out, "--detach")
	}
	return out
}

// Create creates a new container and returns its pid if it was created successfully
func (r *Runc) Create(context context.Context, id, bundle string, opts *CreateOpts) error {
	args := []string{"create", "--bundle", bundle}
	if opts != nil {
		args = append(args, opts.args()...)
	}
	cmd := r.command(context, append(args, id)...)
	if opts != nil {
		opts.setSTDIO(cmd)
	}
	return runOrError(cmd)
}

// Start will start an already created container
func (r *Runc) Start(context context.Context, id string) error {
	return runOrError(r.command(context, "start", id))
}

type ExecOpts struct {
	IO
	PidFile       string
	Uid           int
	Gid           int
	Cwd           string
	Tty           bool
	ConsoleSocket string
	Detach        bool
}

func (o *ExecOpts) args() (out []string) {
	out = append(out, "--user", fmt.Sprintf("%d:%d", o.Uid, o.Gid))
	if o.Tty {
		out = append(out, "--tty")
	}
	if o.ConsoleSocket != "" {
		out = append(out, "--console", o.ConsoleSocket)
	}
	if o.Cwd != "" {
		out = append(out, "--cwd", o.Cwd)
	}
	if o.Detach {
		out = append(out, "--detach")
	}
	if o.PidFile != "" {
		out = append(out, "--pid-file", o.PidFile)
	}
	return out
}

// Exec executres and additional process inside the container based on a full
// OCI Process specification
func (r *Runc) Exec(context context.Context, id string, spec specs.Process, opts *ExecOpts) error {
	f, err := ioutil.TempFile("", "-process")
	if err != nil {
		return err
	}
	defer os.Remove(f.Name())
	err = json.NewEncoder(f).Encode(spec)
	f.Close()
	if err != nil {
		return err
	}
	args := []string{"exec", "--process", f.Name()}
	if opts != nil {
		args = append(args, opts.args()...)
	}
	cmd := r.command(context, append(args, id)...)
	if opts != nil {
		opts.setSTDIO(cmd)
	}
	return runOrError(cmd)
}

// Run runs the create, start, delete lifecycle of the container
// and returns its exit status after it has exited
func (r *Runc) Run(context context.Context, id, bundle string, opts *CreateOpts) (int, error) {
	args := []string{"run", "--bundle", bundle}
	if opts != nil {
		args = append(args, opts.args()...)
	}
	cmd := r.command(context, append(args, id)...)
	if opts != nil {
		opts.setSTDIO(cmd)
	}
	if err := cmd.Start(); err != nil {
		return -1, err
	}
	status, err := cmd.Process.Wait()
	if err != nil {
		return -1, err
	}
	return status.Sys().(syscall.WaitStatus).ExitStatus(), nil
}

// Delete deletes the container
func (r *Runc) Delete(context context.Context, id string) error {
	return runOrError(r.command(context, "delete", id))
}

// KillOpts specifies options for killing a container and its processes
type KillOpts struct {
	All bool
}

func (o *KillOpts) args() (out []string) {
	if o.All {
		out = append(out, "--all")
	}
	return out
}

// Kill sends the specified signal to the container
func (r *Runc) Kill(context context.Context, id string, sig int, opts *KillOpts) error {
	args := []string{
		"kill",
	}
	if opts != nil {
		args = append(args, opts.args()...)
	}
	return runOrError(r.command(context, append(args, id, strconv.Itoa(sig))...))
}

// Stats return the stats for a container like cpu, memory, and io
func (r *Runc) Stats(context context.Context, id string) (*Stats, error) {
	cmd := r.command(context, "events", "--stats", id)
	rd, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}
	defer func() {
		rd.Close()
		cmd.Wait()
	}()
	if err := cmd.Start(); err != nil {
		return nil, err
	}
	var e Event
	if err := json.NewDecoder(rd).Decode(&e); err != nil {
		return nil, err
	}
	return e.Stats, nil
}

// Events returns an event stream from runc for a container with stats and OOM notifications
func (r *Runc) Events(context context.Context, id string, interval time.Duration) (chan *Event, error) {
	cmd := r.command(context, "events", fmt.Sprintf("--interval=%ds", int(interval.Seconds())), id)
	rd, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}
	if err := cmd.Start(); err != nil {
		rd.Close()
		return nil, err
	}
	var (
		dec = json.NewDecoder(rd)
		c   = make(chan *Event, 128)
	)
	go func() {
		defer func() {
			close(c)
			rd.Close()
			cmd.Wait()
		}()
		for {
			var e Event
			if err := dec.Decode(&e); err != nil {
				if err == io.EOF {
					return
				}
				e = Event{
					Type: "error",
					Err:  err,
				}
			}
			c <- &e
		}
	}()
	return c, nil
}

// Pause the container with the provided id
func (r *Runc) Pause(context context.Context, id string) error {
	return runOrError(r.command(context, "pause", id))
}

// Resume the container with the provided id
func (r *Runc) Resume(context context.Context, id string) error {
	return runOrError(r.command(context, "resume", id))
}

// Ps lists all the processes inside the container returning their pids
func (r *Runc) Ps(context context.Context, id string) ([]int, error) {
	data, err := r.command(context, "ps", "--format", "json", id).CombinedOutput()
	if err != nil {
		return nil, fmt.Errorf("%s: %s", err, data)
	}
	var pids []int
	if err := json.Unmarshal(data, &pids); err != nil {
		return nil, err
	}
	return pids, nil
}

func (r *Runc) args() (out []string) {
	if r.Root != "" {
		out = append(out, "--root", r.Root)
	}
	if r.Debug {
		out = append(out, "--debug")
	}
	if r.Log != "" {
		out = append(out, "--log", r.Log)
	}
	if r.LogFormat != none {
		out = append(out, "--log-format", string(r.LogFormat))
	}
	return out
}

func (r *Runc) command(context context.Context, args ...string) *exec.Cmd {
	command := r.Command
	if command == "" {
		command = DefaultCommand
	}
	cmd := exec.CommandContext(context, command, append(r.args(), args...)...)
	if r.PdeathSignal != 0 {
		cmd.SysProcAttr = &syscall.SysProcAttr{
			Pdeathsig: r.PdeathSignal,
		}
	}
	return cmd
}
<|endoftext|>


<commit_before>package runc

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"syscall"
	"time"

	specs "github.com/opencontainers/runtime-spec/specs-go"
)

// Format is the type of log formatting options avaliable
type Format string

const (
	none Format = ""
	JSON Format = "json"
	Text Format = "text"
	// DefaultCommand is the default command for Runc
	DefaultCommand string = "runc"
)

// Runc is the client to the runc cli
type Runc struct {
	//If command is empty, DefaultCommand is used
	Command      string
	Root         string
	Debug        bool
	Log          string
	LogFormat    Format
	PdeathSignal syscall.Signal
}

// List returns all containers created inside the provided runc root directory
func (r *Runc) List(context context.Context) ([]*Container, error) {
	data, err := r.command(context, "list", "--format=json").Output()
	if err != nil {
		return nil, err
	}
	var out []*Container
	if err := json.Unmarshal(data, &out); err != nil {
		return nil, err
	}
	return out, nil
}

// State returns the state for the container provided by id
func (r *Runc) State(context context.Context, id string) (*Container, error) {
	data, err := r.command(context, "state", id).CombinedOutput()
	if err != nil {
		return nil, fmt.Errorf("%s: %s", err, data)
	}
	var c Container
	if err := json.Unmarshal(data, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

type CreateOpts struct {
	IO
	// PidFile is a path to where a pid file should be created
	PidFile       string
	ConsoleSocket *ConsoleSocket
	Detach        bool
	NoPivot       bool
	NoNewKeyring  bool
}

func (o *CreateOpts) args() (out []string, err error) {
	if o.PidFile != "" {
		abs, err := filepath.Abs(o.PidFile)
		if err != nil {
			return nil, err
		}
		out = append(out, "--pid-file", abs)
	}
	if o.ConsoleSocket != nil {
		out = append(out, "--console-socket", o.ConsoleSocket.Path())
	}
	if o.NoPivot {
		out = append(out, "--no-pivot")
	}
	if o.NoNewKeyring {
		out = append(out, "--no-new-keyring")
	}
	if o.Detach {
		out = append(out, "--detach")
	}
	return out, nil
}

// Create creates a new container and returns its pid if it was created successfully
func (r *Runc) Create(context context.Context, id, bundle string, opts *CreateOpts) error {
	args := []string{"create", "--bundle", bundle}
	if opts != nil {
		oargs, err := opts.args()
		if err != nil {
			return err
		}
		args = append(args, oargs...)
	}
	cmd := r.command(context, append(args, id)...)
	if opts != nil && opts.IO != nil {
		opts.Set(cmd)
	}
	if cmd.Stdout == nil && cmd.Stderr == nil {
		data, err := cmd.CombinedOutput()
		if err != nil {
			return fmt.Errorf("%s: %s", err, data)
		}
		return nil
	}
	if err := cmd.Start(); err != nil {
		return err
	}
	if opts != nil && opts.IO != nil {
		if c, ok := opts.IO.(StartCloser); ok {
			if err := c.CloseAfterStart(); err != nil {
				return err
			}
		}
	}
	return cmd.Wait()
}

// Start will start an already created container
func (r *Runc) Start(context context.Context, id string) error {
	return runOrError(r.command(context, "start", id))
}

type ExecOpts struct {
	IO
	PidFile       string
	ConsoleSocket *ConsoleSocket
	Detach        bool
}

func (o *ExecOpts) args() (out []string, err error) {
	if o.ConsoleSocket != nil {
		out = append(out, "--console-socket", o.ConsoleSocket.Path())
	}
	if o.Detach {
		out = append(out, "--detach")
	}
	if o.PidFile != "" {
		abs, err := filepath.Abs(o.PidFile)
		if err != nil {
			return nil, err
		}
		out = append(out, "--pid-file", abs)
	}
	return out, nil
}

// Exec executres and additional process inside the container based on a full
// OCI Process specification
func (r *Runc) Exec(context context.Context, id string, spec specs.Process, opts *ExecOpts) error {
	f, err := ioutil.TempFile("", "-process")
	if err != nil {
		return err
	}
	defer os.Remove(f.Name())
	err = json.NewEncoder(f).Encode(spec)
	f.Close()
	if err != nil {
		return err
	}
	args := []string{"exec", "--process", f.Name()}
	if opts != nil {
		oargs, err := opts.args()
		if err != nil {
			return err
		}
		args = append(args, oargs...)
	}
	cmd := r.command(context, append(args, id)...)
	if opts != nil && opts.IO != nil {
		opts.Set(cmd)
	}
	if cmd.Stdout == nil && cmd.Stderr == nil {
		data, err := cmd.CombinedOutput()
		if err != nil {
			return fmt.Errorf("%s: %s", err, data)
		}
		return nil
	}
	if err := cmd.Start(); err != nil {
		return err
	}
	if opts != nil && opts.IO != nil {
		if c, ok := opts.IO.(StartCloser); ok {
			if err := c.CloseAfterStart(); err != nil {
				return err
			}
		}
	}
	return cmd.Wait()
}

// Run runs the create, start, delete lifecycle of the container
// and returns its exit status after it has exited
func (r *Runc) Run(context context.Context, id, bundle string, opts *CreateOpts) (int, error) {
	args := []string{"run", "--bundle", bundle}
	if opts != nil {
		oargs, err := opts.args()
		if err != nil {
			return -1, err
		}
		args = append(args, oargs...)
	}
	cmd := r.command(context, append(args, id)...)
	if opts != nil {
		opts.Set(cmd)
	}
	if err := cmd.Start(); err != nil {
		return -1, err
	}
	status, err := cmd.Process.Wait()
	if err != nil {
		return -1, err
	}
	return status.Sys().(syscall.WaitStatus).ExitStatus(), nil
}

// Delete deletes the container
func (r *Runc) Delete(context context.Context, id string) error {
	return runOrError(r.command(context, "delete", id))
}

// KillOpts specifies options for killing a container and its processes
type KillOpts struct {
	All bool
}

func (o *KillOpts) args() (out []string) {
	if o.All {
		out = append(out, "--all")
	}
	return out
}

// Kill sends the specified signal to the container
func (r *Runc) Kill(context context.Context, id string, sig int, opts *KillOpts) error {
	args := []string{
		"kill",
	}
	if opts != nil {
		args = append(args, opts.args()...)
	}
	return runOrError(r.command(context, append(args, id, strconv.Itoa(sig))...))
}

// Stats return the stats for a container like cpu, memory, and io
func (r *Runc) Stats(context context.Context, id string) (*Stats, error) {
	cmd := r.command(context, "events", "--stats", id)
	rd, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}
	defer func() {
		rd.Close()
		cmd.Wait()
	}()
	if err := cmd.Start(); err != nil {
		return nil, err
	}
	var e Event
	if err := json.NewDecoder(rd).Decode(&e); err != nil {
		return nil, err
	}
	return e.Stats, nil
}

// Events returns an event stream from runc for a container with stats and OOM notifications
func (r *Runc) Events(context context.Context, id string, interval time.Duration) (chan *Event, error) {
	cmd := r.command(context, "events", fmt.Sprintf("--interval=%ds", int(interval.Seconds())), id)
	rd, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}
	if err := cmd.Start(); err != nil {
		rd.Close()
		return nil, err
	}
	var (
		dec = json.NewDecoder(rd)
		c   = make(chan *Event, 128)
	)
	go func() {
		defer func() {
			close(c)
			rd.Close()
			cmd.Wait()
		}()
		for {
			var e Event
			if err := dec.Decode(&e); err != nil {
				if err == io.EOF {
					return
				}
				e = Event{
					Type: "error",
					Err:  err,
				}
			}
			c <- &e
		}
	}()
	return c, nil
}

// Pause the container with the provided id
func (r *Runc) Pause(context context.Context, id string) error {
	return runOrError(r.command(context, "pause", id))
}

// Resume the container with the provided id
func (r *Runc) Resume(context context.Context, id string) error {
	return runOrError(r.command(context, "resume", id))
}

// Ps lists all the processes inside the container returning their pids
func (r *Runc) Ps(context context.Context, id string) ([]int, error) {
	data, err := r.command(context, "ps", "--format", "json", id).CombinedOutput()
	if err != nil {
		return nil, fmt.Errorf("%s: %s", err, data)
	}
	var pids []int
	if err := json.Unmarshal(data, &pids); err != nil {
		return nil, err
	}
	return pids, nil
}

func (r *Runc) args() (out []string) {
	if r.Root != "" {
		out = append(out, "--root", r.Root)
	}
	if r.Debug {
		out = append(out, "--debug")
	}
	if r.Log != "" {
		out = append(out, "--log", r.Log)
	}
	if r.LogFormat != none {
		out = append(out, "--log-format", string(r.LogFormat))
	}
	return out
}

func (r *Runc) command(context context.Context, args ...string) *exec.Cmd {
	command := r.Command
	if command == "" {
		command = DefaultCommand
	}
	cmd := exec.CommandContext(context, command, append(r.args(), args...)...)
	if r.PdeathSignal != 0 {
		cmd.SysProcAttr = &syscall.SysProcAttr{
			Pdeathsig: r.PdeathSignal,
		}
	}
	return cmd
}
<commit_msg>Remove - from temp process name<commit_after>package runc

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"syscall"
	"time"

	specs "github.com/opencontainers/runtime-spec/specs-go"
)

// Format is the type of log formatting options avaliable
type Format string

const (
	none Format = ""
	JSON Format = "json"
	Text Format = "text"
	// DefaultCommand is the default command for Runc
	DefaultCommand string = "runc"
)

// Runc is the client to the runc cli
type Runc struct {
	//If command is empty, DefaultCommand is used
	Command      string
	Root         string
	Debug        bool
	Log          string
	LogFormat    Format
	PdeathSignal syscall.Signal
}

// List returns all containers created inside the provided runc root directory
func (r *Runc) List(context context.Context) ([]*Container, error) {
	data, err := r.command(context, "list", "--format=json").Output()
	if err != nil {
		return nil, err
	}
	var out []*Container
	if err := json.Unmarshal(data, &out); err != nil {
		return nil, err
	}
	return out, nil
}

// State returns the state for the container provided by id
func (r *Runc) State(context context.Context, id string) (*Container, error) {
	data, err := r.command(context, "state", id).CombinedOutput()
	if err != nil {
		return nil, fmt.Errorf("%s: %s", err, data)
	}
	var c Container
	if err := json.Unmarshal(data, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

type CreateOpts struct {
	IO
	// PidFile is a path to where a pid file should be created
	PidFile       string
	ConsoleSocket *ConsoleSocket
	Detach        bool
	NoPivot       bool
	NoNewKeyring  bool
}

func (o *CreateOpts) args() (out []string, err error) {
	if o.PidFile != "" {
		abs, err := filepath.Abs(o.PidFile)
		if err != nil {
			return nil, err
		}
		out = append(out, "--pid-file", abs)
	}
	if o.ConsoleSocket != nil {
		out = append(out, "--console-socket", o.ConsoleSocket.Path())
	}
	if o.NoPivot {
		out = append(out, "--no-pivot")
	}
	if o.NoNewKeyring {
		out = append(out, "--no-new-keyring")
	}
	if o.Detach {
		out = append(out, "--detach")
	}
	return out, nil
}

// Create creates a new container and returns its pid if it was created successfully
func (r *Runc) Create(context context.Context, id, bundle string, opts *CreateOpts) error {
	args := []string{"create", "--bundle", bundle}
	if opts != nil {
		oargs, err := opts.args()
		if err != nil {
			return err
		}
		args = append(args, oargs...)
	}
	cmd := r.command(context, append(args, id)...)
	if opts != nil && opts.IO != nil {
		opts.Set(cmd)
	}
	if cmd.Stdout == nil && cmd.Stderr == nil {
		data, err := cmd.CombinedOutput()
		if err != nil {
			return fmt.Errorf("%s: %s", err, data)
		}
		return nil
	}
	if err := cmd.Start(); err != nil {
		return err
	}
	if opts != nil && opts.IO != nil {
		if c, ok := opts.IO.(StartCloser); ok {
			if err := c.CloseAfterStart(); err != nil {
				return err
			}
		}
	}
	return cmd.Wait()
}

// Start will start an already created container
func (r *Runc) Start(context context.Context, id string) error {
	return runOrError(r.command(context, "start", id))
}

type ExecOpts struct {
	IO
	PidFile       string
	ConsoleSocket *ConsoleSocket
	Detach        bool
}

func (o *ExecOpts) args() (out []string, err error) {
	if o.ConsoleSocket != nil {
		out = append(out, "--console-socket", o.ConsoleSocket.Path())
	}
	if o.Detach {
		out = append(out, "--detach")
	}
	if o.PidFile != "" {
		abs, err := filepath.Abs(o.PidFile)
		if err != nil {
			return nil, err
		}
		out = append(out, "--pid-file", abs)
	}
	return out, nil
}

// Exec executres and additional process inside the container based on a full
// OCI Process specification
func (r *Runc) Exec(context context.Context, id string, spec specs.Process, opts *ExecOpts) error {
	f, err := ioutil.TempFile("", "runc-process")
	if err != nil {
		return err
	}
	defer os.Remove(f.Name())
	err = json.NewEncoder(f).Encode(spec)
	f.Close()
	if err != nil {
		return err
	}
	args := []string{"exec", "--process", f.Name()}
	if opts != nil {
		oargs, err := opts.args()
		if err != nil {
			return err
		}
		args = append(args, oargs...)
	}
	cmd := r.command(context, append(args, id)...)
	if opts != nil && opts.IO != nil {
		opts.Set(cmd)
	}
	if cmd.Stdout == nil && cmd.Stderr == nil {
		data, err := cmd.CombinedOutput()
		if err != nil {
			return fmt.Errorf("%s: %s", err, data)
		}
		return nil
	}
	if err := cmd.Start(); err != nil {
		return err
	}
	if opts != nil && opts.IO != nil {
		if c, ok := opts.IO.(StartCloser); ok {
			if err := c.CloseAfterStart(); err != nil {
				return err
			}
		}
	}
	return cmd.Wait()
}

// Run runs the create, start, delete lifecycle of the container
// and returns its exit status after it has exited
func (r *Runc) Run(context context.Context, id, bundle string, opts *CreateOpts) (int, error) {
	args := []string{"run", "--bundle", bundle}
	if opts != nil {
		oargs, err := opts.args()
		if err != nil {
			return -1, err
		}
		args = append(args, oargs...)
	}
	cmd := r.command(context, append(args, id)...)
	if opts != nil {
		opts.Set(cmd)
	}
	if err := cmd.Start(); err != nil {
		return -1, err
	}
	status, err := cmd.Process.Wait()
	if err != nil {
		return -1, err
	}
	return status.Sys().(syscall.WaitStatus).ExitStatus(), nil
}

// Delete deletes the container
func (r *Runc) Delete(context context.Context, id string) error {
	return runOrError(r.command(context, "delete", id))
}

// KillOpts specifies options for killing a container and its processes
type KillOpts struct {
	All bool
}

func (o *KillOpts) args() (out []string) {
	if o.All {
		out = append(out, "--all")
	}
	return out
}

// Kill sends the specified signal to the container
func (r *Runc) Kill(context context.Context, id string, sig int, opts *KillOpts) error {
	args := []string{
		"kill",
	}
	if opts != nil {
		args = append(args, opts.args()...)
	}
	return runOrError(r.command(context, append(args, id, strconv.Itoa(sig))...))
}

// Stats return the stats for a container like cpu, memory, and io
func (r *Runc) Stats(context context.Context, id string) (*Stats, error) {
	cmd := r.command(context, "events", "--stats", id)
	rd, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}
	defer func() {
		rd.Close()
		cmd.Wait()
	}()
	if err := cmd.Start(); err != nil {
		return nil, err
	}
	var e Event
	if err := json.NewDecoder(rd).Decode(&e); err != nil {
		return nil, err
	}
	return e.Stats, nil
}

// Events returns an event stream from runc for a container with stats and OOM notifications
func (r *Runc) Events(context context.Context, id string, interval time.Duration) (chan *Event, error) {
	cmd := r.command(context, "events", fmt.Sprintf("--interval=%ds", int(interval.Seconds())), id)
	rd, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}
	if err := cmd.Start(); err != nil {
		rd.Close()
		return nil, err
	}
	var (
		dec = json.NewDecoder(rd)
		c   = make(chan *Event, 128)
	)
	go func() {
		defer func() {
			close(c)
			rd.Close()
			cmd.Wait()
		}()
		for {
			var e Event
			if err := dec.Decode(&e); err != nil {
				if err == io.EOF {
					return
				}
				e = Event{
					Type: "error",
					Err:  err,
				}
			}
			c <- &e
		}
	}()
	return c, nil
}

// Pause the container with the provided id
func (r *Runc) Pause(context context.Context, id string) error {
	return runOrError(r.command(context, "pause", id))
}

// Resume the container with the provided id
func (r *Runc) Resume(context context.Context, id string) error {
	return runOrError(r.command(context, "resume", id))
}

// Ps lists all the processes inside the container returning their pids
func (r *Runc) Ps(context context.Context, id string) ([]int, error) {
	data, err := r.command(context, "ps", "--format", "json", id).CombinedOutput()
	if err != nil {
		return nil, fmt.Errorf("%s: %s", err, data)
	}
	var pids []int
	if err := json.Unmarshal(data, &pids); err != nil {
		return nil, err
	}
	return pids, nil
}

func (r *Runc) args() (out []string) {
	if r.Root != "" {
		out = append(out, "--root", r.Root)
	}
	if r.Debug {
		out = append(out, "--debug")
	}
	if r.Log != "" {
		out = append(out, "--log", r.Log)
	}
	if r.LogFormat != none {
		out = append(out, "--log-format", string(r.LogFormat))
	}
	return out
}

func (r *Runc) command(context context.Context, args ...string) *exec.Cmd {
	command := r.Command
	if command == "" {
		command = DefaultCommand
	}
	cmd := exec.CommandContext(context, command, append(r.args(), args...)...)
	if r.PdeathSignal != 0 {
		cmd.SysProcAttr = &syscall.SysProcAttr{
			Pdeathsig: r.PdeathSignal,
		}
	}
	return cmd
}
<|endoftext|>


<commit_before>package imgmeta

import (
	//"fmt"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
)

var supportedExts = regexp.MustCompile(".+\\.(?i)(jpg|png)$")

type ImgMeta struct {
	BasePath string
	Name     string
	Path     string
	Meta     map[string]string
}

func (im *ImgMeta) RelPath() string {
	rel, _ := filepath.Rel(im.BasePath, im.FullPath())
	return rel
}

func (im *ImgMeta) FullPath() string {
	return im.Path + "/" + im.Name
}

type ByImageName []*ImgMeta

func (b ByImageName) Len() int {
	return len(b)
}
func (b ByImageName) Swap(i, j int) {
	b[i], b[j] = b[j], b[i]
}
func (b ByImageName) Less(i, j int) bool {
	return b[i].Name < b[j].Name
}

type ImgFolder struct {
	BasePath string
	Name     string
	Path     string
	Folders  []*ImgFolder
	Images   []*ImgMeta
}

func (im *ImgFolder) RelPath() string {
	rel, _ := filepath.Rel(im.BasePath, im.Path)
	return rel
}

func _matchFolder(folders []*ImgFolder, folder string) *ImgFolder {
	for _, f := range folders {
		if f.Name == folder {
			return f
		}
	}
	return nil
}

func (im *ImgFolder) Find(path string) *ImgFolder {
	var parent *ImgFolder
	folders := strings.Split(strings.Trim(path, "/"), "/")
	parent = im
	for _, f := range folders {
		p := _matchFolder(parent.Folders, f)
		if p == nil {
			return nil
		}
		parent = p
	}
	return parent
}

type ByFolderName []*ImgFolder

func (b ByFolderName) Len() int {
	return len(b)
}
func (b ByFolderName) Swap(i, j int) {
	b[i], b[j] = b[j], b[i]
}
func (b ByFolderName) Less(i, j int) bool {
	return b[i].Name < b[j].Name
}

func scanFolder(path string, basePath string, name string) (*ImgFolder, error) {
	re := &ImgFolder{
		BasePath: basePath,
		Path:     path,
		Folders:  make([]*ImgFolder, 0),
		Name:     name,
	}
	folder, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	files, err := folder.Readdir(-1)

	if err != nil {
		return nil, err
	}
	for _, fi := range files {
		// skip all dot files/folders
		if fi.Name()[0] == '.' {
			continue
		}
		if fi.IsDir() {
			subfolder, err := scanFolder(path+"/"+fi.Name(), basePath, fi.Name())
			if err == nil {
				re.Folders = append(re.Folders, subfolder)
			}
		}
		if supportedExts.MatchString(fi.Name()) {
			img := &ImgMeta{Name: fi.Name(), Path: path, BasePath: basePath}
			meta := getCache(basePath, img.RelPath())
			if meta == nil {
				meta = getMeta(img.FullPath())
				err = saveCache(basePath, img.RelPath(), meta)
			}
			img.Meta = meta
			re.Images = append(re.Images, img)
		}
	}
	if len(re.Images) > 0 {
		sort.Sort(ByImageName(re.Images))
	}
	if len(re.Folders) > 0 {
		sort.Reverse(ByFolderName(re.Folders))
	}
	return re, nil
}

func Scan(path string) (*ImgFolder, error) {
	p, err := filepath.Abs(path)
	if err != nil {
		return nil, err
	}
	return scanFolder(p, p, "")
}
<commit_msg>Misunderstand reverse<commit_after>package imgmeta

import (
	//"fmt"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
)

var supportedExts = regexp.MustCompile(".+\\.(?i)(jpg|png)$")

type ImgMeta struct {
	BasePath string
	Name     string
	Path     string
	Meta     map[string]string
}

func (im *ImgMeta) RelPath() string {
	rel, _ := filepath.Rel(im.BasePath, im.FullPath())
	return rel
}

func (im *ImgMeta) FullPath() string {
	return im.Path + "/" + im.Name
}

type ByImageName []*ImgMeta

func (b ByImageName) Len() int {
	return len(b)
}
func (b ByImageName) Swap(i, j int) {
	b[i], b[j] = b[j], b[i]
}
func (b ByImageName) Less(i, j int) bool {
	return b[i].Name < b[j].Name
}

type ImgFolder struct {
	BasePath string
	Name     string
	Path     string
	Folders  []*ImgFolder
	Images   []*ImgMeta
}

func (im *ImgFolder) RelPath() string {
	rel, _ := filepath.Rel(im.BasePath, im.Path)
	return rel
}

func _matchFolder(folders []*ImgFolder, folder string) *ImgFolder {
	for _, f := range folders {
		if f.Name == folder {
			return f
		}
	}
	return nil
}

func (im *ImgFolder) Find(path string) *ImgFolder {
	var parent *ImgFolder
	folders := strings.Split(strings.Trim(path, "/"), "/")
	parent = im
	for _, f := range folders {
		p := _matchFolder(parent.Folders, f)
		if p == nil {
			return nil
		}
		parent = p
	}
	return parent
}

type ByFolderName []*ImgFolder

func (b ByFolderName) Len() int {
	return len(b)
}
func (b ByFolderName) Swap(i, j int) {
	b[i], b[j] = b[j], b[i]
}
func (b ByFolderName) Less(i, j int) bool {
	return b[i].Name < b[j].Name
}

func scanFolder(path string, basePath string, name string) (*ImgFolder, error) {
	re := &ImgFolder{
		BasePath: basePath,
		Path:     path,
		Folders:  make([]*ImgFolder, 0),
		Name:     name,
	}
	folder, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	files, err := folder.Readdir(-1)

	if err != nil {
		return nil, err
	}
	for _, fi := range files {
		// skip all dot files/folders
		if fi.Name()[0] == '.' {
			continue
		}
		if fi.IsDir() {
			subfolder, err := scanFolder(path+"/"+fi.Name(), basePath, fi.Name())
			if err == nil {
				re.Folders = append(re.Folders, subfolder)
			}
		}
		if supportedExts.MatchString(fi.Name()) {
			img := &ImgMeta{Name: fi.Name(), Path: path, BasePath: basePath}
			meta := getCache(basePath, img.RelPath())
			if meta == nil {
				meta = getMeta(img.FullPath())
				err = saveCache(basePath, img.RelPath(), meta)
			}
			img.Meta = meta
			re.Images = append(re.Images, img)
		}
	}
	if len(re.Images) > 0 {
		sort.Sort(ByImageName(re.Images))
	}
	if len(re.Folders) > 0 {
		sort.Sort(sort.Reverse(ByFolderName(re.Folders)))
	}
	return re, nil
}

func Scan(path string) (*ImgFolder, error) {
	p, err := filepath.Abs(path)
	if err != nil {
		return nil, err
	}
	return scanFolder(p, p, "")
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"crypto/sha256"
	"crypto/tls"
	"encoding/base64"
	"io"
	"io/ioutil"
	"log"
	"math/rand"
	"net"
	"net/http"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	quic "github.com/phuslu/quic-go"
	"github.com/phuslu/quic-go/h2quic"
)

type ScanRecord struct {
	IP         string
	MatchHosts []string
	PingRTT    time.Duration
	SSLRTT     time.Duration

	httpVerifyTimeout time.Duration
}
type ScanRecordArray []*ScanRecord

type ScanOptions struct {
	Config *GScanConfig

	recordMutex sync.Mutex
	hostsMutex  sync.Mutex
	inputHosts  HostIPTable
	records     ScanRecordArray

	scanCounter int32
}

func (options *ScanOptions) AddRecord(rec *ScanRecord) {
	options.recordMutex.Lock()
	if nil == options.records {
		options.records = make(ScanRecordArray, 0)
	}
	options.records = append(options.records, rec)
	options.recordMutex.Unlock()
	log.Printf("Found a record: IP=%s, SSLRTT=%fs\n", rec.IP, rec.SSLRTT.Seconds())
}

func (options *ScanOptions) IncScanCounter() {
	atomic.AddInt32(&(options.scanCounter), 1)
	if options.scanCounter%1000 == 0 {
		log.Printf("Scanned %d IPs, Found %d records\n", options.scanCounter, options.RecordSize())
	}
}

func (options *ScanOptions) RecordSize() int {
	options.recordMutex.Lock()
	defer options.recordMutex.Unlock()
	return len(options.records)
}

func (options *ScanOptions) SSLMatchHosts(conn *tls.Conn) []string {
	hosts := make([]string, 0)
	options.hostsMutex.Lock()
	for _, host := range options.inputHosts {
		testhost := host.Host
		if strings.Contains(testhost, ".appspot.com") {
			testhost = "appengine.google.com"
		} else if strings.Contains(testhost, "ggpht.com") {
			testhost = "googleusercontent.com"
		} else if strings.Contains(testhost, ".books.google.com") {
			testhost = "books.google.com"
		} else if strings.Contains(testhost, ".googleusercontent.com") {
			testhost = "googleusercontent.com"
		}
		if conn.VerifyHostname(testhost) == nil {
			hosts = append(hosts, host.Host)
		}
	}
	options.hostsMutex.Unlock()
	dest := make([]string, len(hosts))
	perm := rand.Perm(len(hosts))
	for i, v := range perm {
		dest[v] = hosts[i]
	}
	hosts = dest
	return hosts
}

func (options *ScanOptions) HaveHostInRecords(host string) bool {
	options.hostsMutex.Lock()
	defer options.hostsMutex.Unlock()
	_, exists := options.inputHosts[host]
	return !exists
}

func (options *ScanOptions) RemoveFromInputHosts(hosts []string) {
	options.hostsMutex.Lock()
	for _, host := range hosts {
		delete(options.inputHosts, host)
	}
	options.hostsMutex.Unlock()
}

func matchHostnames(pattern, host string) bool {
	if len(pattern) == 0 || len(host) == 0 {
		return false
	}
	patternParts := strings.Split(pattern, ".")
	hostParts := strings.Split(host, ".")

	if len(patternParts) != len(hostParts) {
		return false
	}

	for i, patternPart := range patternParts {
		if patternPart == "*" {
			continue
		}
		if patternPart != hostParts[i] {
			return false
		}
	}
	return true
}

var (
	tlsCfg = &tls.Config{
		InsecureSkipVerify: true,
	}
	g2pkp, _ = base64.StdEncoding.DecodeString("7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=")
	g3pkp, _ = base64.StdEncoding.DecodeString("f8NnEFZxQ4ExFOhSN7EiFWtiudZQVD2oY60uauV/n78=")
)

func testip_once(ip string, options *ScanOptions, record *ScanRecord) bool {
	start := time.Now()

	pingRTT := (options.Config.ScanMinPingRTT + options.Config.ScanMaxPingRTT) / 2
	if options.Config.VerifyPing {
		err := Ping(ip, options.Config.ScanMaxPingRTT)
		if err != nil {
			return false
		}
		end := time.Now()
		if nil == err {
			if options.Config.ScanMinPingRTT > 0 && end.Sub(start) < options.Config.ScanMinPingRTT {
				return false
			}
			pingRTT = end.Sub(start)
		}
	}
	record.PingRTT = record.PingRTT + pingRTT

	addr := net.JoinHostPort(ip, "443")

	start = time.Now()
	success := make(chan bool, 5)

	go func() {
		<-time.After(options.Config.ScanMaxSSLRTT + 500*time.Millisecond)
		success <- false
	}()

	var quicSessn quic.Session
	defer func() {
		if quicSessn != nil {
			quicSessn.Close(nil)
		}
	}()

	tr := &h2quic.RoundTripper{DisableCompression: true}
	defer tr.Close()

	quicCfg := &quic.Config{
		HandshakeTimeout: options.Config.ScanMaxSSLRTT - 500*time.Millisecond,
		// IdleTimeout:      options.Config.ScanMaxSSLRTT,
		KeepAlive: false,
	}

	udpAddr, err := net.ResolveUDPAddr("udp", addr)
	if err != nil {
		return false
	}
	udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
	if err != nil {
		return false
	}

	udpConn.SetDeadline(time.Now().Add(options.Config.ScanMaxSSLRTT))
	defer udpConn.Close()

	go func(chan bool) {
		var err error
		// quicSessn, err = quic.DialAddr(addr, tlsCfg, quicCfg)
		quicSessn, err = quic.Dial(udpConn, udpAddr, addr, tlsCfg, quicCfg)
		if err != nil {
			success <- false
			return
		}
		// 证书验证
		cs := quicSessn.ConnectionState()
		if cs == nil {
			success <- false
			return
		}
		pcs := cs.PeerCertificates
		if len(pcs) < 2 {
			success <- false
			return
		}
		pkp := sha256.Sum256(pcs[1].RawSubjectPublicKeyInfo)
		if !bytes.Equal(g2pkp, pkp[:]) && !bytes.Equal(g3pkp, pkp[:]) {
			success <- false
			return
		}

		tr.DialAddr = func(hostname string, tlsConfig *tls.Config, config *quic.Config) (quic.Session, error) {
			return quicSessn, err
		}
		hclient := &http.Client{
			Transport: tr,
		}

		for _, verifyHost := range options.Config.ScanGoogleIP.HTTPVerifyHosts {
			req, _ := http.NewRequest(http.MethodHead, "https://"+verifyHost, nil)
			req.Close = true
			resp, err := hclient.Do(req)
			if resp != nil && resp.Body != nil {
				io.Copy(ioutil.Discard, resp.Body)
				resp.Body.Close()
			}
			if nil != err || resp.StatusCode >= 400 {
				success <- false
				return
			}
		}
		success <- true
	}(success)

	if <-success == false {
		return false
	}

	sslRTT := time.Since(start)
	record.SSLRTT = record.SSLRTT + sslRTT
	return true
}

func testip(ip string, options *ScanOptions) *ScanRecord {
	record := new(ScanRecord)
	record.IP = ip
	for i := 0; i < options.Config.ScanCountPerIP; i++ {
		if !testip_once(ip, options, record) {
			return nil
		}
	}
	record.PingRTT = record.PingRTT / time.Duration(options.Config.ScanCountPerIP)
	record.SSLRTT = record.SSLRTT / time.Duration(options.Config.ScanCountPerIP)
	return record
}

func testip_worker(ch chan string, options *ScanOptions, wg *sync.WaitGroup) {
	for ip := range ch {
		record := testip(ip, options)
		if nil != record {
			if !options.Config.scanIP {
				options.RemoveFromInputHosts(record.MatchHosts)
			}
			options.AddRecord(record)
		}
		options.IncScanCounter()
	}
	wg.Done()
}
<commit_msg>添加 G3 ECC 证书验证<commit_after>package main

import (
	"bytes"
	"crypto/sha256"
	"crypto/tls"
	"encoding/base64"
	"io"
	"io/ioutil"
	"log"
	"math/rand"
	"net"
	"net/http"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	quic "github.com/phuslu/quic-go"
	"github.com/phuslu/quic-go/h2quic"
)

type ScanRecord struct {
	IP         string
	MatchHosts []string
	PingRTT    time.Duration
	SSLRTT     time.Duration

	httpVerifyTimeout time.Duration
}
type ScanRecordArray []*ScanRecord

type ScanOptions struct {
	Config *GScanConfig

	recordMutex sync.Mutex
	hostsMutex  sync.Mutex
	inputHosts  HostIPTable
	records     ScanRecordArray

	scanCounter int32
}

func (options *ScanOptions) AddRecord(rec *ScanRecord) {
	options.recordMutex.Lock()
	if nil == options.records {
		options.records = make(ScanRecordArray, 0)
	}
	options.records = append(options.records, rec)
	options.recordMutex.Unlock()
	log.Printf("Found a record: IP=%s, SSLRTT=%fs\n", rec.IP, rec.SSLRTT.Seconds())
}

func (options *ScanOptions) IncScanCounter() {
	atomic.AddInt32(&(options.scanCounter), 1)
	if options.scanCounter%1000 == 0 {
		log.Printf("Scanned %d IPs, Found %d records\n", options.scanCounter, options.RecordSize())
	}
}

func (options *ScanOptions) RecordSize() int {
	options.recordMutex.Lock()
	defer options.recordMutex.Unlock()
	return len(options.records)
}

func (options *ScanOptions) SSLMatchHosts(conn *tls.Conn) []string {
	hosts := make([]string, 0)
	options.hostsMutex.Lock()
	for _, host := range options.inputHosts {
		testhost := host.Host
		if strings.Contains(testhost, ".appspot.com") {
			testhost = "appengine.google.com"
		} else if strings.Contains(testhost, "ggpht.com") {
			testhost = "googleusercontent.com"
		} else if strings.Contains(testhost, ".books.google.com") {
			testhost = "books.google.com"
		} else if strings.Contains(testhost, ".googleusercontent.com") {
			testhost = "googleusercontent.com"
		}
		if conn.VerifyHostname(testhost) == nil {
			hosts = append(hosts, host.Host)
		}
	}
	options.hostsMutex.Unlock()
	dest := make([]string, len(hosts))
	perm := rand.Perm(len(hosts))
	for i, v := range perm {
		dest[v] = hosts[i]
	}
	hosts = dest
	return hosts
}

func (options *ScanOptions) HaveHostInRecords(host string) bool {
	options.hostsMutex.Lock()
	defer options.hostsMutex.Unlock()
	_, exists := options.inputHosts[host]
	return !exists
}

func (options *ScanOptions) RemoveFromInputHosts(hosts []string) {
	options.hostsMutex.Lock()
	for _, host := range hosts {
		delete(options.inputHosts, host)
	}
	options.hostsMutex.Unlock()
}

func matchHostnames(pattern, host string) bool {
	if len(pattern) == 0 || len(host) == 0 {
		return false
	}
	patternParts := strings.Split(pattern, ".")
	hostParts := strings.Split(host, ".")

	if len(patternParts) != len(hostParts) {
		return false
	}

	for i, patternPart := range patternParts {
		if patternPart == "*" {
			continue
		}
		if patternPart != hostParts[i] {
			return false
		}
	}
	return true
}

var (
	tlsCfg = &tls.Config{
		InsecureSkipVerify: true,
	}
	g2pkp, _ = base64.StdEncoding.DecodeString("7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=")
	g3pkp, _ = base64.StdEncoding.DecodeString("f8NnEFZxQ4ExFOhSN7EiFWtiudZQVD2oY60uauV/n78=")
	g3ecc, _ = base64.StdEncoding.DecodeString("ekG8/PoSqjfKunOaS9iIzR3hZAJptWJKV7CgyriO+MA=")
)

func testip_once(ip string, options *ScanOptions, record *ScanRecord) bool {
	start := time.Now()

	pingRTT := (options.Config.ScanMinPingRTT + options.Config.ScanMaxPingRTT) / 2
	if options.Config.VerifyPing {
		err := Ping(ip, options.Config.ScanMaxPingRTT)
		if err != nil {
			return false
		}
		end := time.Now()
		if nil == err {
			if options.Config.ScanMinPingRTT > 0 && end.Sub(start) < options.Config.ScanMinPingRTT {
				return false
			}
			pingRTT = end.Sub(start)
		}
	}
	record.PingRTT = record.PingRTT + pingRTT

	addr := net.JoinHostPort(ip, "443")

	start = time.Now()
	success := make(chan bool, 5)

	go func() {
		<-time.After(options.Config.ScanMaxSSLRTT + 500*time.Millisecond)
		success <- false
	}()

	var quicSessn quic.Session
	defer func() {
		if quicSessn != nil {
			quicSessn.Close(nil)
		}
	}()

	tr := &h2quic.RoundTripper{DisableCompression: true}
	defer tr.Close()

	quicCfg := &quic.Config{
		HandshakeTimeout: options.Config.ScanMaxSSLRTT - 500*time.Millisecond,
		// IdleTimeout:      options.Config.ScanMaxSSLRTT,
		KeepAlive: false,
	}

	udpAddr, err := net.ResolveUDPAddr("udp", addr)
	if err != nil {
		return false
	}
	udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
	if err != nil {
		return false
	}

	udpConn.SetDeadline(time.Now().Add(options.Config.ScanMaxSSLRTT))
	defer udpConn.Close()

	go func(chan bool) {
		var err error
		// quicSessn, err = quic.DialAddr(addr, tlsCfg, quicCfg)
		quicSessn, err = quic.Dial(udpConn, udpAddr, addr, tlsCfg, quicCfg)
		if err != nil {
			success <- false
			return
		}
		// 证书验证
		cs := quicSessn.ConnectionState()
		if cs == nil {
			success <- false
			return
		}
		pcs := cs.PeerCertificates
		if len(pcs) < 2 {
			success <- false
			return
		}
		pkp := sha256.Sum256(pcs[1].RawSubjectPublicKeyInfo)
		if !bytes.Equal(g2pkp, pkp[:]) && !bytes.Equal(g3pkp, pkp[:]) && !bytes.Equal(g3ecc, pkp[:]) {
			success <- false
			return
		}

		tr.DialAddr = func(hostname string, tlsConfig *tls.Config, config *quic.Config) (quic.Session, error) {
			return quicSessn, err
		}
		hclient := &http.Client{
			Transport: tr,
		}

		for _, verifyHost := range options.Config.ScanGoogleIP.HTTPVerifyHosts {
			req, _ := http.NewRequest(http.MethodHead, "https://"+verifyHost, nil)
			req.Close = true
			resp, err := hclient.Do(req)
			if resp != nil && resp.Body != nil {
				io.Copy(ioutil.Discard, resp.Body)
				resp.Body.Close()
			}
			if nil != err || resp.StatusCode >= 400 {
				success <- false
				return
			}
		}
		success <- true
	}(success)

	if <-success == false {
		return false
	}

	sslRTT := time.Since(start)
	record.SSLRTT = record.SSLRTT + sslRTT
	return true
}

func testip(ip string, options *ScanOptions) *ScanRecord {
	record := new(ScanRecord)
	record.IP = ip
	for i := 0; i < options.Config.ScanCountPerIP; i++ {
		if !testip_once(ip, options, record) {
			return nil
		}
	}
	record.PingRTT = record.PingRTT / time.Duration(options.Config.ScanCountPerIP)
	record.SSLRTT = record.SSLRTT / time.Duration(options.Config.ScanCountPerIP)
	return record
}

func testip_worker(ch chan string, options *ScanOptions, wg *sync.WaitGroup) {
	for ip := range ch {
		record := testip(ip, options)
		if nil != record {
			if !options.Config.scanIP {
				options.RemoveFromInputHosts(record.MatchHosts)
			}
			options.AddRecord(record)
		}
		options.IncScanCounter()
	}
	wg.Done()
}
<|endoftext|>