<commit_before>package appfs

import (
	"bytes"
	"compress/gzip"
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"io"
	"io/ioutil"
	"mime"
	"net/http"
	"os"
	"path"
	"strconv"
	"strings"

	"github.com/andybalholm/brotli"
	"github.com/cozy/cozy-stack/pkg/consts"
	web_utils "github.com/cozy/cozy-stack/pkg/utils"
	"github.com/ncw/swift"
	"github.com/spf13/afero"
)

// FileServer interface defines a way to access and serve the application's
// data files.
type FileServer interface {
	Open(slug, version, shasum, file string) (io.ReadCloser, error)
	FilesList(slug, version, shasum string) ([]string, error)
	ServeFileContent(w http.ResponseWriter, req *http.Request,
		slug, version, shasum, file string) error
}

type swiftServer struct {
	c         *swift.Connection
	container string
}

type aferoServer struct {
	mkPath func(slug, version, shasum, file string) string
	fs     afero.Fs
}

type brotliReadCloser struct {
	br *brotli.Reader
	cl io.Closer
}

// brotli.Reader has no Close method. This little wrapper adds a method to
// close the underlying reader.
func newBrotliReadCloser(r io.ReadCloser) (io.ReadCloser, error) {
	br := brotli.NewReader(r)
	return brotliReadCloser{br: br, cl: r}, nil
}

func (r brotliReadCloser) Read(b []byte) (int, error) {
	return r.br.Read(b)
}

func (r brotliReadCloser) Close() error {
	return r.cl.Close()
}

type gzipReadCloser struct {
	gr *gzip.Reader
	cl io.Closer
}

// The Close method of gzip.Reader does not closes the underlying reader. This
// little wrapper does the closing.
func newGzipReadCloser(r io.ReadCloser) (io.ReadCloser, error) {
	gr, err := gzip.NewReader(r)
	if err != nil {
		return nil, err
	}
	return gzipReadCloser{gr: gr, cl: r}, nil
}

func (g gzipReadCloser) Read(b []byte) (int, error) {
	return g.gr.Read(b)
}

func (g gzipReadCloser) Close() error {
	err1 := g.gr.Close()
	err2 := g.cl.Close()
	if err1 != nil {
		return err1
	}
	if err2 != nil {
		return err2
	}
	return nil
}

// NewSwiftFileServer returns provides the apps.FileServer implementation
// using the swift backend as file server.
func NewSwiftFileServer(conn *swift.Connection, appsType consts.AppType) FileServer {
	return &swiftServer{
		c:         conn,
		container: containerName(appsType),
	}
}

func (s *swiftServer) Open(slug, version, shasum, file string) (io.ReadCloser, error) {
	objName := s.makeObjectName(slug, version, shasum, file)
	f, h, err := s.c.ObjectOpen(s.container, objName, false, nil)
	if err != nil {
		return nil, wrapSwiftErr(err)
	}
	o := h.ObjectMetadata()
	contentEncoding := o["content-encoding"]
	if contentEncoding == "br" {
		return newBrotliReadCloser(f)
	} else if contentEncoding == "gzip" {
		return newGzipReadCloser(f)
	}
	return f, nil
}

func (s *swiftServer) ServeFileContent(w http.ResponseWriter, req *http.Request, slug, version, shasum, file string) error {
	objName := s.makeObjectName(slug, version, shasum, file)
	f, h, err := s.c.ObjectOpen(s.container, objName, false, nil)
	if err != nil {
		return wrapSwiftErr(err)
	}
	defer f.Close()

	if checkETag := req.Header.Get("Cache-Control") == ""; checkETag {
		etag := fmt.Sprintf(`"%s"`, h["Etag"][:10])
		if web_utils.CheckPreconditions(w, req, etag) {
			return nil
		}
		w.Header().Set("Etag", etag)
	}

	var r io.Reader = f
	contentLength := h["Content-Length"]
	contentType := h["Content-Type"]
	o := h.ObjectMetadata()
	contentEncoding := o["content-encoding"]
	if contentEncoding == "br" {
		if acceptBrotliEncoding(req) {
			w.Header().Set("Content-Encoding", "br")
		} else {
			contentLength = o["original-content-length"]
			r = brotli.NewReader(f)
		}
	} else if contentEncoding == "gzip" {
		if acceptGzipEncoding(req) {
			w.Header().Set("Content-Encoding", "gzip")
		} else {
			contentLength = o["original-content-length"]
			var gr *gzip.Reader
			gr, err = gzip.NewReader(f)
			if err != nil {
				return err
			}
			defer gr.Close()
			r = gr
		}
	}

	ext := path.Ext(file)
	if contentType == "" {
		contentType = mime.TypeByExtension(ext)
	}
	if contentType == "text/xml" && ext == ".svg" {
		// override for files with text/xml content because of leading <?xml tag
		contentType = "image/svg+xml"
	}

	size, _ := strconv.ParseInt(contentLength, 10, 64)
	web_utils.ServeContent(w, req, contentType, size, r)
	return nil
}

func (s *swiftServer) makeObjectName(slug, version, shasum, file string) string {
	basepath := path.Join(slug, version)
	if shasum != "" {
		basepath += "-" + shasum
	}
	return path.Join(basepath, file)
}

func (s *swiftServer) FilesList(slug, version, shasum string) ([]string, error) {
	prefix := s.makeObjectName(slug, version, shasum, "") + "/"
	names, err := s.c.ObjectNamesAll(s.container, &swift.ObjectsOpts{
		Prefix: prefix,
	})
	if err != nil {
		return nil, err
	}
	filtered := names[:0]
	for _, n := range names {
		n = strings.TrimPrefix(n, prefix)
		if n != "" {
			filtered = append(filtered, n)
		}
	}
	return filtered, nil
}

// NewAferoFileServer returns a simple wrapper of the afero.Fs interface that
// provides the apps.FileServer interface.
//
// You can provide a makePath method to define how the file name should be
// created from the application's slug, version and file name. If not provided,
// the standard VFS concatenation (starting with vfs.WebappsDirName) is used.
func NewAferoFileServer(fs afero.Fs, makePath func(slug, version, shasum, file string) string) FileServer {
	if makePath == nil {
		makePath = defaultMakePath
	}
	return &aferoServer{
		mkPath: makePath,
		fs:     fs,
	}
}

func (s *aferoServer) Open(slug, version, shasum, file string) (io.ReadCloser, error) {
	isGzipped := false
	filepath := s.mkPath(slug, version, shasum, file)
	f, err := s.open(filepath + ".br")
	if os.IsNotExist(err) {
		isGzipped = true
		f, err = s.open(filepath + ".gz")
	}
	if err != nil {
		return nil, err
	}
	if isGzipped {
		return newGzipReadCloser(f)
	}
	return newBrotliReadCloser(f)
}

func (s *aferoServer) open(filepath string) (io.ReadCloser, error) {
	return s.fs.Open(filepath)
}

func (s *aferoServer) ServeFileContent(w http.ResponseWriter, req *http.Request, slug, version, shasum, file string) error {
	filepath := s.mkPath(slug, version, shasum, file)
	return s.serveFileContent(w, req, filepath)
}

func (s *aferoServer) serveFileContent(w http.ResponseWriter, req *http.Request, filepath string) error {
	isGzipped := false
	f, err := s.fs.Open(filepath + ".br")
	if os.IsNotExist(err) {
		isGzipped = true
		f, err = s.fs.Open(filepath + ".gz")
	}
	if err != nil {
		return err
	}
	defer f.Close()

	var content io.Reader
	var size int64
	if checkEtag := req.Header.Get("Cache-Control") == ""; checkEtag {
		var b []byte
		h := md5.New()
		b, err = ioutil.ReadAll(f)
		if err != nil {
			return err
		}
		etag := fmt.Sprintf(`"%s"`, hex.EncodeToString(h.Sum(nil)))
		if web_utils.CheckPreconditions(w, req, etag) {
			return nil
		}
		w.Header().Set("Etag", etag)
		size = int64(len(b))
		content = bytes.NewReader(b)
	} else {
		size, err = f.Seek(0, io.SeekEnd)
		if err != nil {
			return err
		}
		content = f
	}
	_, err = f.Seek(0, io.SeekStart)
	if err != nil {
		return err
	}

	if isGzipped {
		if acceptGzipEncoding(req) {
			w.Header().Set("Content-Encoding", "gzip")
		} else {
			var gr *gzip.Reader
			var b []byte
			gr, err = gzip.NewReader(content)
			if err != nil {
				return err
			}
			defer gr.Close()
			b, err = ioutil.ReadAll(gr)
			if err != nil {
				return err
			}
			size = int64(len(b))
			content = bytes.NewReader(b)
		}
	} else {
		if acceptBrotliEncoding(req) {
			w.Header().Set("Content-Encoding", "br")
		} else {
			var b []byte
			br := brotli.NewReader(content)
			b, err = ioutil.ReadAll(br)
			if err != nil {
				return err
			}
			size = int64(len(b))
			content = bytes.NewReader(b)
		}
	}

	contentType := mime.TypeByExtension(path.Ext(filepath))
	web_utils.ServeContent(w, req, contentType, size, content)
	return nil
}

func (s *aferoServer) FilesList(slug, version, shasum string) ([]string, error) {
	var names []string
	rootPath := s.mkPath(slug, version, shasum, "")
	err := afero.Walk(s.fs, rootPath, func(path string, infos os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if !infos.IsDir() {
			name := strings.TrimPrefix(path, rootPath)
			name = strings.TrimSuffix(name, ".br")
			names = append(names, name)
		}
		return nil
	})
	return names, err
}

func defaultMakePath(slug, version, shasum, file string) string {
	basepath := path.Join("/", slug, version)
	if shasum != "" {
		basepath += "-" + shasum
	}
	filepath := path.Join("/", file)
	return path.Join(basepath, filepath)
}

func acceptBrotliEncoding(req *http.Request) bool {
	return strings.Contains(req.Header.Get("Accept-Encoding"), "br")
}

func acceptGzipEncoding(req *http.Request) bool {
	return strings.Contains(req.Header.Get("Accept-Encoding"), "gzip")
}

func containerName(appsType consts.AppType) string {
	switch appsType {
	case consts.WebappType:
		return "apps-web"
	case consts.KonnectorType:
		return "apps-konnectors"
	}
	panic("Unknown AppType")
}

func wrapSwiftErr(err error) error {
	if err == swift.ObjectNotFound || err == swift.ContainerNotFound {
		return os.ErrNotExist
	}
	return err
}
<commit_msg>Fix cozy-stack serve --appdir (#2902)<commit_after>package appfs

import (
	"bytes"
	"compress/gzip"
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"io"
	"io/ioutil"
	"mime"
	"net/http"
	"os"
	"path"
	"strconv"
	"strings"

	"github.com/andybalholm/brotli"
	"github.com/cozy/cozy-stack/pkg/consts"
	web_utils "github.com/cozy/cozy-stack/pkg/utils"
	"github.com/ncw/swift"
	"github.com/spf13/afero"
)

// FileServer interface defines a way to access and serve the application's
// data files.
type FileServer interface {
	Open(slug, version, shasum, file string) (io.ReadCloser, error)
	FilesList(slug, version, shasum string) ([]string, error)
	ServeFileContent(w http.ResponseWriter, req *http.Request,
		slug, version, shasum, file string) error
}

type swiftServer struct {
	c         *swift.Connection
	container string
}

type aferoServer struct {
	mkPath func(slug, version, shasum, file string) string
	fs     afero.Fs
}

type brotliReadCloser struct {
	br *brotli.Reader
	cl io.Closer
}

// brotli.Reader has no Close method. This little wrapper adds a method to
// close the underlying reader.
func newBrotliReadCloser(r io.ReadCloser) (io.ReadCloser, error) {
	br := brotli.NewReader(r)
	return brotliReadCloser{br: br, cl: r}, nil
}

func (r brotliReadCloser) Read(b []byte) (int, error) {
	return r.br.Read(b)
}

func (r brotliReadCloser) Close() error {
	return r.cl.Close()
}

type gzipReadCloser struct {
	gr *gzip.Reader
	cl io.Closer
}

// The Close method of gzip.Reader does not closes the underlying reader. This
// little wrapper does the closing.
func newGzipReadCloser(r io.ReadCloser) (io.ReadCloser, error) {
	gr, err := gzip.NewReader(r)
	if err != nil {
		return nil, err
	}
	return gzipReadCloser{gr: gr, cl: r}, nil
}

func (g gzipReadCloser) Read(b []byte) (int, error) {
	return g.gr.Read(b)
}

func (g gzipReadCloser) Close() error {
	err1 := g.gr.Close()
	err2 := g.cl.Close()
	if err1 != nil {
		return err1
	}
	if err2 != nil {
		return err2
	}
	return nil
}

// NewSwiftFileServer returns provides the apps.FileServer implementation
// using the swift backend as file server.
func NewSwiftFileServer(conn *swift.Connection, appsType consts.AppType) FileServer {
	return &swiftServer{
		c:         conn,
		container: containerName(appsType),
	}
}

func (s *swiftServer) Open(slug, version, shasum, file string) (io.ReadCloser, error) {
	objName := s.makeObjectName(slug, version, shasum, file)
	f, h, err := s.c.ObjectOpen(s.container, objName, false, nil)
	if err != nil {
		return nil, wrapSwiftErr(err)
	}
	o := h.ObjectMetadata()
	contentEncoding := o["content-encoding"]
	if contentEncoding == "br" {
		return newBrotliReadCloser(f)
	} else if contentEncoding == "gzip" {
		return newGzipReadCloser(f)
	}
	return f, nil
}

func (s *swiftServer) ServeFileContent(w http.ResponseWriter, req *http.Request, slug, version, shasum, file string) error {
	objName := s.makeObjectName(slug, version, shasum, file)
	f, h, err := s.c.ObjectOpen(s.container, objName, false, nil)
	if err != nil {
		return wrapSwiftErr(err)
	}
	defer f.Close()

	if checkETag := req.Header.Get("Cache-Control") == ""; checkETag {
		etag := fmt.Sprintf(`"%s"`, h["Etag"][:10])
		if web_utils.CheckPreconditions(w, req, etag) {
			return nil
		}
		w.Header().Set("Etag", etag)
	}

	var r io.Reader = f
	contentLength := h["Content-Length"]
	contentType := h["Content-Type"]
	o := h.ObjectMetadata()
	contentEncoding := o["content-encoding"]
	if contentEncoding == "br" {
		if acceptBrotliEncoding(req) {
			w.Header().Set("Content-Encoding", "br")
		} else {
			contentLength = o["original-content-length"]
			r = brotli.NewReader(f)
		}
	} else if contentEncoding == "gzip" {
		if acceptGzipEncoding(req) {
			w.Header().Set("Content-Encoding", "gzip")
		} else {
			contentLength = o["original-content-length"]
			var gr *gzip.Reader
			gr, err = gzip.NewReader(f)
			if err != nil {
				return err
			}
			defer gr.Close()
			r = gr
		}
	}

	ext := path.Ext(file)
	if contentType == "" {
		contentType = mime.TypeByExtension(ext)
	}
	if contentType == "text/xml" && ext == ".svg" {
		// override for files with text/xml content because of leading <?xml tag
		contentType = "image/svg+xml"
	}

	size, _ := strconv.ParseInt(contentLength, 10, 64)
	web_utils.ServeContent(w, req, contentType, size, r)
	return nil
}

func (s *swiftServer) makeObjectName(slug, version, shasum, file string) string {
	basepath := path.Join(slug, version)
	if shasum != "" {
		basepath += "-" + shasum
	}
	return path.Join(basepath, file)
}

func (s *swiftServer) FilesList(slug, version, shasum string) ([]string, error) {
	prefix := s.makeObjectName(slug, version, shasum, "") + "/"
	names, err := s.c.ObjectNamesAll(s.container, &swift.ObjectsOpts{
		Prefix: prefix,
	})
	if err != nil {
		return nil, err
	}
	filtered := names[:0]
	for _, n := range names {
		n = strings.TrimPrefix(n, prefix)
		if n != "" {
			filtered = append(filtered, n)
		}
	}
	return filtered, nil
}

// NewAferoFileServer returns a simple wrapper of the afero.Fs interface that
// provides the apps.FileServer interface.
//
// You can provide a makePath method to define how the file name should be
// created from the application's slug, version and file name. If not provided,
// the standard VFS concatenation (starting with vfs.WebappsDirName) is used.
func NewAferoFileServer(fs afero.Fs, makePath func(slug, version, shasum, file string) string) FileServer {
	if makePath == nil {
		makePath = defaultMakePath
	}
	return &aferoServer{
		mkPath: makePath,
		fs:     fs,
	}
}

const (
	uncompressed = iota + 1
	gzipped
	brotlied
)

// openFile opens the give filepath. By default, it is a file compressed with
// brotli (.br), but it can be a file compressed with gzip (.gz, for apps that
// were installed before brotli compression was enabled), or uncompressed (for
// app development with cozy-stack serve --appdir).
func (s *aferoServer) openFile(filepath string) (afero.File, int, error) {
	compression := brotlied
	f, err := s.fs.Open(filepath + ".br")
	if os.IsNotExist(err) {
		compression = gzipped
		f, err = s.fs.Open(filepath + ".gz")
	}
	if os.IsNotExist(err) {
		compression = uncompressed
		f, err = s.fs.Open(filepath)
	}
	return f, compression, err
}

func (s *aferoServer) Open(slug, version, shasum, file string) (io.ReadCloser, error) {
	filepath := s.mkPath(slug, version, shasum, file)
	f, compression, err := s.openFile(filepath)
	if err != nil {
		return nil, err
	}
	switch compression {
	case uncompressed:
		return f, nil
	case gzipped:
		return newGzipReadCloser(f)
	case brotlied:
		return newBrotliReadCloser(f)
	default:
		panic(fmt.Errorf("Unknown compression type: %v", compression))
	}
}

func (s *aferoServer) ServeFileContent(w http.ResponseWriter, req *http.Request, slug, version, shasum, file string) error {
	filepath := s.mkPath(slug, version, shasum, file)
	return s.serveFileContent(w, req, filepath)
}

func (s *aferoServer) serveFileContent(w http.ResponseWriter, req *http.Request, filepath string) error {
	f, compression, err := s.openFile(filepath)
	if err != nil {
		return err
	}
	defer f.Close()

	var content io.Reader
	var size int64
	if checkEtag := req.Header.Get("Cache-Control") == ""; checkEtag {
		var b []byte
		h := md5.New()
		b, err = ioutil.ReadAll(f)
		if err != nil {
			return err
		}
		etag := fmt.Sprintf(`"%s"`, hex.EncodeToString(h.Sum(nil)))
		if web_utils.CheckPreconditions(w, req, etag) {
			return nil
		}
		w.Header().Set("Etag", etag)
		size = int64(len(b))
		content = bytes.NewReader(b)
	} else {
		size, err = f.Seek(0, io.SeekEnd)
		if err != nil {
			return err
		}
		content = f
	}

	switch compression {
	case uncompressed:
		// Nothing to do
	case gzipped:
		if acceptGzipEncoding(req) {
			w.Header().Set("Content-Encoding", "gzip")
		} else {
			var gr *gzip.Reader
			var b []byte
			gr, err = gzip.NewReader(content)
			if err != nil {
				return err
			}
			defer gr.Close()
			b, err = ioutil.ReadAll(gr)
			if err != nil {
				return err
			}
			size = int64(len(b))
			content = bytes.NewReader(b)
		}
	case brotlied:
		if acceptBrotliEncoding(req) {
			w.Header().Set("Content-Encoding", "br")
		} else {
			var b []byte
			br := brotli.NewReader(content)
			b, err = ioutil.ReadAll(br)
			if err != nil {
				return err
			}
			size = int64(len(b))
			content = bytes.NewReader(b)
		}
	default:
		panic(fmt.Errorf("Unknown compression type: %v", compression))
	}

	contentType := mime.TypeByExtension(path.Ext(filepath))
	web_utils.ServeContent(w, req, contentType, size, content)
	return nil
}

func (s *aferoServer) FilesList(slug, version, shasum string) ([]string, error) {
	var names []string
	rootPath := s.mkPath(slug, version, shasum, "")
	err := afero.Walk(s.fs, rootPath, func(path string, infos os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if !infos.IsDir() {
			name := strings.TrimPrefix(path, rootPath)
			name = strings.TrimSuffix(name, ".br")
			names = append(names, name)
		}
		return nil
	})
	return names, err
}

func defaultMakePath(slug, version, shasum, file string) string {
	basepath := path.Join("/", slug, version)
	if shasum != "" {
		basepath += "-" + shasum
	}
	filepath := path.Join("/", file)
	return path.Join(basepath, filepath)
}

func acceptBrotliEncoding(req *http.Request) bool {
	return strings.Contains(req.Header.Get("Accept-Encoding"), "br")
}

func acceptGzipEncoding(req *http.Request) bool {
	return strings.Contains(req.Header.Get("Accept-Encoding"), "gzip")
}

func containerName(appsType consts.AppType) string {
	switch appsType {
	case consts.WebappType:
		return "apps-web"
	case consts.KonnectorType:
		return "apps-konnectors"
	}
	panic("Unknown AppType")
}

func wrapSwiftErr(err error) error {
	if err == swift.ObjectNotFound || err == swift.ContainerNotFound {
		return os.ErrNotExist
	}
	return err
}
<|endoftext|>


<commit_before>/*
Copyright 2018 Google LLC

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 commands

import (
	"fmt"
	"os"
	"os/exec"
	"os/user"
	"strings"
	"syscall"

	kConfig "github.com/GoogleContainerTools/kaniko/pkg/config"
	"github.com/GoogleContainerTools/kaniko/pkg/constants"
	"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
	"github.com/GoogleContainerTools/kaniko/pkg/util"
	v1 "github.com/google/go-containerregistry/pkg/v1"
	"github.com/moby/buildkit/frontend/dockerfile/instructions"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

type RunCommand struct {
	BaseCommand
	cmd *instructions.RunCommand
}

// for testing
var (
	userLookup   = user.Lookup
	userLookupID = user.LookupId
)

func (r *RunCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error {
	return runCommandInExec(config, buildArgs, r.cmd)
}

func runCommandInExec(config *v1.Config, buildArgs *dockerfile.BuildArgs, cmdRun *instructions.RunCommand) error {
	var newCommand []string
	if cmdRun.PrependShell {
		// This is the default shell on Linux
		var shell []string
		if len(config.Shell) > 0 {
			shell = config.Shell
		} else {
			shell = append(shell, "/bin/sh", "-c")
		}

		newCommand = append(shell, strings.Join(cmdRun.CmdLine, " "))
	} else {
		newCommand = cmdRun.CmdLine
		// Find and set absolute path of executable by setting PATH temporary
		replacementEnvs := buildArgs.ReplacementEnvs(config.Env)
		for _, v := range replacementEnvs {
			entry := strings.SplitN(v, "=", 2)
			if entry[0] != "PATH" {
				continue
			}
			oldPath := os.Getenv("PATH")
			defer os.Setenv("PATH", oldPath)
			os.Setenv("PATH", entry[1])
			path, err := exec.LookPath(newCommand[0])
			if err == nil {
				newCommand[0] = path
			}
		}
	}

	logrus.Infof("cmd: %s", newCommand[0])
	logrus.Infof("args: %s", newCommand[1:])

	cmd := exec.Command(newCommand[0], newCommand[1:]...)

	cmd.Dir = setWorkDirIfExists(config.WorkingDir)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	replacementEnvs := buildArgs.ReplacementEnvs(config.Env)
	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

	u := config.User
	userAndGroup := strings.Split(u, ":")
	userStr, err := util.ResolveEnvironmentReplacement(userAndGroup[0], replacementEnvs, false)
	if err != nil {
		return errors.Wrapf(err, "resolving user %s", userAndGroup[0])
	}

	// If specified, run the command as a specific user
	if userStr != "" {
		cmd.SysProcAttr.Credential, err = util.SyscallCredentials(userStr)
		if err != nil {
			return errors.Wrap(err, "credentials")
		}
	}

	env, err := addDefaultHOME(userStr, replacementEnvs)
	if err != nil {
		return errors.Wrap(err, "adding default HOME variable")
	}

	cmd.Env = env

	logrus.Infof("Running: %s", cmd.Args)
	if err := cmd.Start(); err != nil {
		return errors.Wrap(err, "starting command")
	}

	pgid, err := syscall.Getpgid(cmd.Process.Pid)
	if err != nil {
		return errors.Wrap(err, "getting group id for process")
	}
	if err := cmd.Wait(); err != nil {
		return errors.Wrap(err, "waiting for process to exit")
	}

	//it's not an error if there are no grandchildren
	if err := syscall.Kill(-pgid, syscall.SIGKILL); err != nil && err.Error() != "no such process" {
		return err
	}
	return nil
}

// addDefaultHOME adds the default value for HOME if it isn't already set
func addDefaultHOME(u string, envs []string) ([]string, error) {
	for _, env := range envs {
		split := strings.SplitN(env, "=", 2)
		if split[0] == constants.HOME {
			return envs, nil
		}
	}

	// If user isn't set, set default value of HOME
	if u == "" || u == constants.RootUser {
		return append(envs, fmt.Sprintf("%s=%s", constants.HOME, constants.DefaultHOMEValue)), nil
	}

	// If user is set to username, set value of HOME to /home/${user}
	// Otherwise the user is set to uid and HOME is /
	userObj, err := userLookup(u)
	if err != nil {
		if uo, e := userLookupID(u); e == nil {
			userObj = uo
		} else {
			return nil, err
		}
	}

	return append(envs, fmt.Sprintf("%s=%s", constants.HOME, userObj.HomeDir)), nil
}

// String returns some information about the command for the image config
func (r *RunCommand) String() string {
	return r.cmd.String()
}

func (r *RunCommand) FilesToSnapshot() []string {
	return nil
}

func (r *RunCommand) ProvidesFilesToSnapshot() bool {
	return false
}

// CacheCommand returns true since this command should be cached
func (r *RunCommand) CacheCommand(img v1.Image) DockerCommand {

	return &CachingRunCommand{
		img:       img,
		cmd:       r.cmd,
		extractFn: util.ExtractFile,
	}
}

func (r *RunCommand) MetadataOnly() bool {
	return false
}

func (r *RunCommand) RequiresUnpackedFS() bool {
	return true
}

func (r *RunCommand) ShouldCacheOutput() bool {
	return true
}

type CachingRunCommand struct {
	BaseCommand
	caching
	img            v1.Image
	extractedFiles []string
	cmd            *instructions.RunCommand
	extractFn      util.ExtractFunction
}

func (cr *CachingRunCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error {
	logrus.Infof("Found cached layer, extracting to filesystem")
	var err error

	if cr.img == nil {
		return errors.New(fmt.Sprintf("command image is nil %v", cr.String()))
	}

	layers, err := cr.img.Layers()
	if err != nil {
		return errors.Wrap(err, "retrieving image layers")
	}

	if len(layers) != 1 {
		return errors.New(fmt.Sprintf("expected %d layers but got %d", 1, len(layers)))
	}

	cr.layer = layers[0]

	cr.extractedFiles, err = util.GetFSFromLayers(
		kConfig.RootDir,
		layers,
		util.ExtractFunc(cr.extractFn),
		util.IncludeWhiteout(),
	)
	if err != nil {
		return errors.Wrap(err, "extracting fs from image")
	}

	return nil
}

func (cr *CachingRunCommand) FilesToSnapshot() []string {
	f := cr.extractedFiles
	logrus.Debugf("%d files extracted by caching run command", len(f))
	logrus.Tracef("Extracted files: %s", f)

	return f
}

func (cr *CachingRunCommand) String() string {
	if cr.cmd == nil {
		return "nil command"
	}
	return cr.cmd.String()
}

func (cr *CachingRunCommand) MetadataOnly() bool {
	return false
}

func setWorkDirIfExists(workdir string) string {
	if _, err := os.Lstat(workdir); err == nil {
		return workdir
	}
	return ""
}
<commit_msg>fix gofmt<commit_after>/*
Copyright 2018 Google LLC

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 commands

import (
	"fmt"
	"os"
	"os/exec"
	"os/user"
	"strings"
	"syscall"

	kConfig "github.com/GoogleContainerTools/kaniko/pkg/config"
	"github.com/GoogleContainerTools/kaniko/pkg/constants"
	"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
	"github.com/GoogleContainerTools/kaniko/pkg/util"
	v1 "github.com/google/go-containerregistry/pkg/v1"
	"github.com/moby/buildkit/frontend/dockerfile/instructions"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

type RunCommand struct {
	BaseCommand
	cmd *instructions.RunCommand
}

// for testing
var (
	userLookup   = user.Lookup
	userLookupID = user.LookupId
)

func (r *RunCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error {
	return runCommandInExec(config, buildArgs, r.cmd)
}

func runCommandInExec(config *v1.Config, buildArgs *dockerfile.BuildArgs, cmdRun *instructions.RunCommand) error {
	var newCommand []string
	if cmdRun.PrependShell {
		// This is the default shell on Linux
		var shell []string
		if len(config.Shell) > 0 {
			shell = config.Shell
		} else {
			shell = append(shell, "/bin/sh", "-c")
		}

		newCommand = append(shell, strings.Join(cmdRun.CmdLine, " "))
	} else {
		newCommand = cmdRun.CmdLine
		// Find and set absolute path of executable by setting PATH temporary
		replacementEnvs := buildArgs.ReplacementEnvs(config.Env)
		for _, v := range replacementEnvs {
			entry := strings.SplitN(v, "=", 2)
			if entry[0] != "PATH" {
				continue
			}
			oldPath := os.Getenv("PATH")
			defer os.Setenv("PATH", oldPath)
			os.Setenv("PATH", entry[1])
			path, err := exec.LookPath(newCommand[0])
			if err == nil {
				newCommand[0] = path
			}
		}
	}

	logrus.Infof("cmd: %s", newCommand[0])
	logrus.Infof("args: %s", newCommand[1:])

	cmd := exec.Command(newCommand[0], newCommand[1:]...)

	cmd.Dir = setWorkDirIfExists(config.WorkingDir)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	replacementEnvs := buildArgs.ReplacementEnvs(config.Env)
	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

	u := config.User
	userAndGroup := strings.Split(u, ":")
	userStr, err := util.ResolveEnvironmentReplacement(userAndGroup[0], replacementEnvs, false)
	if err != nil {
		return errors.Wrapf(err, "resolving user %s", userAndGroup[0])
	}

	// If specified, run the command as a specific user
	if userStr != "" {
		cmd.SysProcAttr.Credential, err = util.SyscallCredentials(userStr)
		if err != nil {
			return errors.Wrap(err, "credentials")
		}
	}

	env, err := addDefaultHOME(userStr, replacementEnvs)
	if err != nil {
		return errors.Wrap(err, "adding default HOME variable")
	}

	cmd.Env = env

	logrus.Infof("Running: %s", cmd.Args)
	if err := cmd.Start(); err != nil {
		return errors.Wrap(err, "starting command")
	}

	pgid, err := syscall.Getpgid(cmd.Process.Pid)
	if err != nil {
		return errors.Wrap(err, "getting group id for process")
	}
	if err := cmd.Wait(); err != nil {
		return errors.Wrap(err, "waiting for process to exit")
	}

	//it's not an error if there are no grandchildren
	if err := syscall.Kill(-pgid, syscall.SIGKILL); err != nil && err.Error() != "no such process" {
		return err
	}
	return nil
}

// addDefaultHOME adds the default value for HOME if it isn't already set
func addDefaultHOME(u string, envs []string) ([]string, error) {
	for _, env := range envs {
		split := strings.SplitN(env, "=", 2)
		if split[0] == constants.HOME {
			return envs, nil
		}
	}

	// If user isn't set, set default value of HOME
	if u == "" || u == constants.RootUser {
		return append(envs, fmt.Sprintf("%s=%s", constants.HOME, constants.DefaultHOMEValue)), nil
	}

	// If user is set to username, set value of HOME to /home/${user}
	// Otherwise the user is set to uid and HOME is /
	userObj, err := userLookup(u)
	if err != nil {
		if uo, e := userLookupID(u); e == nil {
			userObj = uo
		} else {
			return nil, err
		}
	}

	return append(envs, fmt.Sprintf("%s=%s", constants.HOME, userObj.HomeDir)), nil
}

// String returns some information about the command for the image config
func (r *RunCommand) String() string {
	return r.cmd.String()
}

func (r *RunCommand) FilesToSnapshot() []string {
	return nil
}

func (r *RunCommand) ProvidesFilesToSnapshot() bool {
	return false
}

// CacheCommand returns true since this command should be cached
func (r *RunCommand) CacheCommand(img v1.Image) DockerCommand {

	return &CachingRunCommand{
		img:       img,
		cmd:       r.cmd,
		extractFn: util.ExtractFile,
	}
}

func (r *RunCommand) MetadataOnly() bool {
	return false
}

func (r *RunCommand) RequiresUnpackedFS() bool {
	return true
}

func (r *RunCommand) ShouldCacheOutput() bool {
	return true
}

type CachingRunCommand struct {
	BaseCommand
	caching
	img            v1.Image
	extractedFiles []string
	cmd            *instructions.RunCommand
	extractFn      util.ExtractFunction
}

func (cr *CachingRunCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error {
	logrus.Infof("Found cached layer, extracting to filesystem")
	var err error

	if cr.img == nil {
		return errors.New(fmt.Sprintf("command image is nil %v", cr.String()))
	}

	layers, err := cr.img.Layers()
	if err != nil {
		return errors.Wrap(err, "retrieving image layers")
	}

	if len(layers) != 1 {
		return errors.New(fmt.Sprintf("expected %d layers but got %d", 1, len(layers)))
	}

	cr.layer = layers[0]

	cr.extractedFiles, err = util.GetFSFromLayers(
		kConfig.RootDir,
		layers,
		util.ExtractFunc(cr.extractFn),
		util.IncludeWhiteout(),
	)
	if err != nil {
		return errors.Wrap(err, "extracting fs from image")
	}

	return nil
}

func (cr *CachingRunCommand) FilesToSnapshot() []string {
	f := cr.extractedFiles
	logrus.Debugf("%d files extracted by caching run command", len(f))
	logrus.Tracef("Extracted files: %s", f)

	return f
}

func (cr *CachingRunCommand) String() string {
	if cr.cmd == nil {
		return "nil command"
	}
	return cr.cmd.String()
}

func (cr *CachingRunCommand) MetadataOnly() bool {
	return false
}

func setWorkDirIfExists(workdir string) string {
	if _, err := os.Lstat(workdir); err == nil {
		return workdir
	}
	return ""
}
<|endoftext|>


<commit_before>package runit

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"syscall"
)

const (
	serviceDir = "/etc/service"
	taiOffset  = 4611686018427387914
	statusLen  = 20

	posTimeStart = 0
	posTimeEnd   = 7
	posPidStart  = 12
	posPidEnd    = 15

	posWant  = 17
	posState = 19

	StateDown   = 0
	StateUp     = 1
	StateFinish = 2
)

var (
	ENoRunsv      = errors.New("runsv not running")
	StateToString = map[int]string{
		StateDown:   "down",
		StateUp:     "up",
		StateFinish: "finish",
	}
)

type SvStatus struct {
	Pid        int
	Duration   int
	State      int
	NormallyUp bool
	Want       int
}

type service struct {
	Name       string
	ServiceDir string
}

func GetServices() ([]*service, error) {
	files, err := ioutil.ReadDir(serviceDir)
	if err != nil {
		return nil, err
	}
	services := []*service{}
	for _, file := range files {
		if file.IsDir() {
			continue
		}
		services = append(services, GetService(file.Name()))
	}
	return services, nil
}

func GetService(name string) *service {
	r := service{Name: name, ServiceDir: serviceDir}
	return &r
}

func (s *service) file(file string) string {
	return fmt.Sprintf("%s/%s/supervise/%s", s.ServiceDir, s.Name, file)
}

func (s *service) runsvRunning() (bool, error) {
	file, err := os.OpenFile(s.file("ok"), os.O_WRONLY, 0)
	if err != nil {
		if err == syscall.ENXIO {
			return false, nil
		}
		return false, err
	}
	file.Close()
	return true, nil
}

func (s *service) status() ([]byte, error) {
	file, err := os.Open(s.file("status"))
	defer file.Close()
	if err != nil {
		return nil, err
	}
	status := make([]byte, statusLen)
	_, err = file.Read(status)
	return status, err
}

func (s *service) NormallyUp() bool {
	_, err := os.Stat(s.file("down"))
	return err != nil
}

func (s *service) Status() (*SvStatus, error) {
	running, err := s.runsvRunning()
	if err != nil {
		return nil, err
	}

	if !running {
		return nil, ENoRunsv
	}

	status, err := s.status()
	if err != nil {
		return nil, err
	}

	var pid int
	pid = int(status[posPidEnd])
	for i := posPidEnd - 1; i >= posPidStart; i-- {
		pid <<= 8
		pid += int(status[i])
	}

	tai := int64(status[posTimeStart])
	for i := posTimeStart + 1; i <= posTimeEnd; i++ {
		tai <<= 8
		tai += int64(status[i])
	}
	state := status[posState] // 0: down, 1: run, 2: finish

	tv := &syscall.Timeval{}
	if err := syscall.Gettimeofday(tv); err != nil {
		return nil, err
	}
	sS := SvStatus{
		Pid:        pid,
		Duration:   int(tv.Sec - (tai - taiOffset)),
		State:      int(state),
		NormallyUp: s.NormallyUp(),
	}

	switch status[posWant] {
	case 'u':
		sS.Want = StateUp
	case 'd':
		sS.Want = StateDown
	}

	return &sS, nil
}
<commit_msg>Remove check for running runsv<commit_after>package runit

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"syscall"
)

const (
	serviceDir = "/etc/service"
	taiOffset  = 4611686018427387914
	statusLen  = 20

	posTimeStart = 0
	posTimeEnd   = 7
	posPidStart  = 12
	posPidEnd    = 15

	posWant  = 17
	posState = 19

	StateDown   = 0
	StateUp     = 1
	StateFinish = 2
)

var (
	ENoRunsv      = errors.New("runsv not running")
	StateToString = map[int]string{
		StateDown:   "down",
		StateUp:     "up",
		StateFinish: "finish",
	}
)

type SvStatus struct {
	Pid        int
	Duration   int
	State      int
	NormallyUp bool
	Want       int
}

type service struct {
	Name       string
	ServiceDir string
}

func GetServices() ([]*service, error) {
	files, err := ioutil.ReadDir(serviceDir)
	if err != nil {
		return nil, err
	}
	services := []*service{}
	for _, file := range files {
		if file.IsDir() {
			continue
		}
		services = append(services, GetService(file.Name()))
	}
	return services, nil
}

func GetService(name string) *service {
	r := service{Name: name, ServiceDir: serviceDir}
	return &r
}

func (s *service) file(file string) string {
	return fmt.Sprintf("%s/%s/supervise/%s", s.ServiceDir, s.Name, file)
}

func (s *service) runsvRunning() (bool, error) {
	file, err := os.OpenFile(s.file("ok"), os.O_WRONLY, 0)
	if err != nil {
		if err == syscall.ENXIO {
			return false, nil
		}
		return false, err
	}
	file.Close()
	return true, nil
}

func (s *service) status() ([]byte, error) {
	file, err := os.Open(s.file("status"))
	if err != nil {
		return nil, err
	}
	defer file.Close()
	status := make([]byte, statusLen)
	_, err = file.Read(status)
	return status, err
}

func (s *service) NormallyUp() bool {
	_, err := os.Stat(s.file("down"))
	return err != nil
}

func (s *service) Status() (*SvStatus, error) {
	status, err := s.status()
	if err != nil {
		return nil, err
	}

	var pid int
	pid = int(status[posPidEnd])
	for i := posPidEnd - 1; i >= posPidStart; i-- {
		pid <<= 8
		pid += int(status[i])
	}

	tai := int64(status[posTimeStart])
	for i := posTimeStart + 1; i <= posTimeEnd; i++ {
		tai <<= 8
		tai += int64(status[i])
	}
	state := status[posState] // 0: down, 1: run, 2: finish

	tv := &syscall.Timeval{}
	if err := syscall.Gettimeofday(tv); err != nil {
		return nil, err
	}
	sS := SvStatus{
		Pid:        pid,
		Duration:   int(tv.Sec - (tai - taiOffset)),
		State:      int(state),
		NormallyUp: s.NormallyUp(),
	}

	switch status[posWant] {
	case 'u':
		sS.Want = StateUp
	case 'd':
		sS.Want = StateDown
	}

	return &sS, nil
}
<|endoftext|>


<commit_before>// Copyright 2016 CodisLabs. All Rights Reserved.
// Licensed under the MIT (MIT-LICENSE.txt) license.

package proxy

import (
	"sync"
	"time"

	"github.com/CodisLabs/codis/pkg/models"
	"github.com/CodisLabs/codis/pkg/utils/errors"
	"github.com/CodisLabs/codis/pkg/utils/log"
	"github.com/CodisLabs/codis/pkg/utils/redis"
)

type Router struct {
	mu sync.RWMutex

	pool map[string]*SharedBackendConn

	slots [models.MaxSlotNum]Slot

	config *Config
	online bool
	closed bool

	ha struct {
		monitor *redis.Sentinel
		masters map[int]string
	}
}

func NewRouter(config *Config) *Router {
	s := &Router{config: config}
	s.pool = make(map[string]*SharedBackendConn)
	for i := range s.slots {
		s.slots[i].id = i
	}
	return s
}

func (s *Router) Start() {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.closed {
		return
	}
	s.online = true
}

func (s *Router) Close() {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.closed {
		return
	}
	s.closed = true

	if s.ha.monitor != nil {
		s.ha.monitor.Cancel()
		s.ha.monitor = nil
	}
	for i := range s.slots {
		s.fillSlot(&models.Slot{Id: i})
	}
}

func (s *Router) GetGroupIds() map[int]bool {
	s.mu.Lock()
	defer s.mu.Unlock()
	var groups = make(map[int]bool)
	for i := range s.slots {
		if gid := s.slots[i].backend.id; gid != 0 {
			groups[gid] = true
		}
		if gid := s.slots[i].migrate.id; gid != 0 {
			groups[gid] = true
		}
	}
	return groups
}

func (s *Router) GetSlots() []*models.Slot {
	s.mu.RLock()
	defer s.mu.RUnlock()
	slots := make([]*models.Slot, len(s.slots))
	for i := range s.slots {
		slots[i] = s.slots[i].snapshot(true)
	}
	return slots
}

func (s *Router) GetSlot(id int) *models.Slot {
	s.mu.RLock()
	defer s.mu.RUnlock()
	if id < 0 || id >= len(s.slots) {
		return nil
	}
	slot := &s.slots[id]
	return slot.snapshot(true)
}

var (
	ErrClosedRouter  = errors.New("use of closed router")
	ErrInvalidSlotId = errors.New("use of invalid slot id")
)

func (s *Router) FillSlot(m *models.Slot) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.closed {
		return ErrClosedRouter
	}
	if m.Id < 0 || m.Id >= len(s.slots) {
		return ErrInvalidSlotId
	}
	s.fillSlot(m)
	return nil
}

func (s *Router) KeepAlive() error {
	s.mu.RLock()
	defer s.mu.RUnlock()
	if s.closed {
		return ErrClosedRouter
	}
	for _, bc := range s.pool {
		bc.KeepAlive()
	}
	return nil
}

func (s *Router) isOnline() bool {
	return s.online && !s.closed
}

func (s *Router) dispatch(r *Request) error {
	hkey := getHashKey(r.Multi, r.OpStr)
	var id = Hash(hkey) % uint32(len(s.slots))
	slot := &s.slots[id]
	return slot.forward(r, hkey)
}

func (s *Router) dispatchSlot(r *Request, id int) error {
	if id < 0 || id >= len(s.slots) {
		return ErrInvalidSlotId
	}
	slot := &s.slots[id]
	return slot.forward(r, nil)
}

func (s *Router) dispatchAddr(r *Request, addr string) bool {
	s.mu.RLock()
	defer s.mu.RUnlock()
	bc := s.getBackendConn(addr, false)
	if bc == nil {
		return false
	} else {
		bc.PushBack(r)
		s.putBackendConn(bc)
		return true
	}
}

func (s *Router) getBackendConn(addr string, create bool) *SharedBackendConn {
	if bc := s.pool[addr]; bc != nil {
		return bc.Retain()
	} else if create {
		bc := NewSharedBackendConn(addr, s.config)
		s.pool[addr] = bc
		return bc
	} else {
		return nil
	}
}

func (s *Router) putBackendConn(bc *SharedBackendConn) {
	if bc != nil && bc.Release() {
		delete(s.pool, bc.Addr())
	}
}

func (s *Router) fillSlot(m *models.Slot) {
	slot := &s.slots[m.Id]
	slot.blockAndWait()

	s.putBackendConn(slot.backend.bc)
	slot.backend.bc = nil
	slot.backend.id = 0
	s.putBackendConn(slot.migrate.bc)
	slot.migrate.bc = nil
	slot.migrate.id = 0
	for i := range slot.replicaGroups {
		for _, bc := range slot.replicaGroups[i] {
			s.putBackendConn(bc)
		}
	}
	slot.replicaGroups = nil

	if addr := m.BackendAddr; len(addr) != 0 {
		slot.backend.bc = s.getBackendConn(addr, true)
		slot.backend.id = m.BackendAddrId
	}
	if from := m.MigrateFrom; len(from) != 0 {
		slot.migrate.bc = s.getBackendConn(from, true)
		slot.migrate.id = m.MigrateFromId
	}
	for i := range m.ReplicaGroups {
		var group []*SharedBackendConn
		for _, addr := range m.ReplicaGroups[i] {
			group = append(group, s.getBackendConn(addr, true))
		}
		slot.replicaGroups = append(slot.replicaGroups, group)
	}

	if !m.Locked {
		slot.unblock()
	}
	if !s.closed {
		if slot.migrate.bc != nil {
			log.Warnf("fill slot %04d, backend.addr = %s, migrate.from = %s, locked = %t",
				slot.id, slot.backend.bc.Addr(), slot.migrate.bc.Addr(), slot.lock.hold)
		} else {
			log.Warnf("fill slot %04d, backend.addr = %s, locked = %t",
				slot.id, slot.backend.bc.Addr(), slot.lock.hold)
		}
	}
}

func (s *Router) SwitchMasters(masters map[int]string) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.closed {
		return ErrClosedRouter
	}
	s.ha.masters = masters

	for i := range s.slots {
		s.trySwitchMaster(i)
	}
	return nil
}

func (s *Router) trySwitchMaster(id int) {
	if len(s.ha.masters) == 0 {
		return
	}
	m := s.slots[id].snapshot(false)

	var refill = false

	if addr := s.ha.masters[m.BackendAddrId]; len(addr) != 0 {
		if addr != m.BackendAddr {
			m.BackendAddr = addr
			refill = true
		}
	}
	if from := s.ha.masters[m.MigrateFromId]; len(from) != 0 {
		if from != m.MigrateFrom {
			m.MigrateFrom = from
			refill = true
		}
	}
	if !refill {
		return
	}
	log.Warnf("HA: fill slot %04d +switch-master", id)
	s.fillSlot(m)
}

func (s *Router) SetSentinels(servers []string) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.closed {
		return ErrClosedProxy
	}
	if s.ha.monitor != nil {
		s.ha.monitor.Cancel()
		s.ha.monitor = nil
	}
	log.Warnf("set sentinels = %v", servers)

	if len(servers) == 0 {
		s.ha.masters = nil
	} else {
		s.ha.monitor = redis.NewSentinel(s.config.ProductName)
		go func(p *redis.Sentinel) {
			for {
				timeout := time.Second * 5
				masters := p.Masters(s.GetGroupIds(), timeout, servers...)
				if p.IsCancelled() {
					return
				}
				s.SwitchMasters(masters)

				expires := time.Minute * 10
				retryAt := time.Now().Add(time.Minute)
				if !p.Subscribe(expires, servers...) {
					for time.Now().Before(retryAt) {
						if p.IsCancelled() {
							return
						}
						time.Sleep(time.Second)
					}
				}
				time.Sleep(time.Millisecond * 250)
			}
		}(s.ha.monitor)
	}
	return nil
}
<commit_msg>proxy: fillSlot should ignore ha masters<commit_after>// Copyright 2016 CodisLabs. All Rights Reserved.
// Licensed under the MIT (MIT-LICENSE.txt) license.

package proxy

import (
	"sync"
	"time"

	"github.com/CodisLabs/codis/pkg/models"
	"github.com/CodisLabs/codis/pkg/utils/errors"
	"github.com/CodisLabs/codis/pkg/utils/log"
	"github.com/CodisLabs/codis/pkg/utils/redis"
)

type Router struct {
	mu sync.RWMutex

	pool map[string]*SharedBackendConn

	slots [models.MaxSlotNum]Slot

	config *Config
	online bool
	closed bool

	ha struct {
		monitor *redis.Sentinel
		masters map[int]string
	}
}

func NewRouter(config *Config) *Router {
	s := &Router{config: config}
	s.pool = make(map[string]*SharedBackendConn)
	for i := range s.slots {
		s.slots[i].id = i
	}
	return s
}

func (s *Router) Start() {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.closed {
		return
	}
	s.online = true
}

func (s *Router) Close() {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.closed {
		return
	}
	s.closed = true

	if s.ha.monitor != nil {
		s.ha.monitor.Cancel()
		s.ha.monitor = nil
	}
	for i := range s.slots {
		s.fillSlot(&models.Slot{Id: i})
	}
}

func (s *Router) GetGroupIds() map[int]bool {
	s.mu.Lock()
	defer s.mu.Unlock()
	var groups = make(map[int]bool)
	for i := range s.slots {
		if gid := s.slots[i].backend.id; gid != 0 {
			groups[gid] = true
		}
		if gid := s.slots[i].migrate.id; gid != 0 {
			groups[gid] = true
		}
	}
	return groups
}

func (s *Router) GetSlots() []*models.Slot {
	s.mu.RLock()
	defer s.mu.RUnlock()
	slots := make([]*models.Slot, len(s.slots))
	for i := range s.slots {
		slots[i] = s.slots[i].snapshot(true)
	}
	return slots
}

func (s *Router) GetSlot(id int) *models.Slot {
	s.mu.RLock()
	defer s.mu.RUnlock()
	if id < 0 || id >= len(s.slots) {
		return nil
	}
	slot := &s.slots[id]
	return slot.snapshot(true)
}

var (
	ErrClosedRouter  = errors.New("use of closed router")
	ErrInvalidSlotId = errors.New("use of invalid slot id")
)

func (s *Router) FillSlot(m *models.Slot) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.closed {
		return ErrClosedRouter
	}
	if m.Id < 0 || m.Id >= len(s.slots) {
		return ErrInvalidSlotId
	}
	s.fillSlot(m)
	return nil
}

func (s *Router) KeepAlive() error {
	s.mu.RLock()
	defer s.mu.RUnlock()
	if s.closed {
		return ErrClosedRouter
	}
	for _, bc := range s.pool {
		bc.KeepAlive()
	}
	return nil
}

func (s *Router) isOnline() bool {
	return s.online && !s.closed
}

func (s *Router) dispatch(r *Request) error {
	hkey := getHashKey(r.Multi, r.OpStr)
	var id = Hash(hkey) % uint32(len(s.slots))
	slot := &s.slots[id]
	return slot.forward(r, hkey)
}

func (s *Router) dispatchSlot(r *Request, id int) error {
	if id < 0 || id >= len(s.slots) {
		return ErrInvalidSlotId
	}
	slot := &s.slots[id]
	return slot.forward(r, nil)
}

func (s *Router) dispatchAddr(r *Request, addr string) bool {
	s.mu.RLock()
	defer s.mu.RUnlock()
	bc := s.getBackendConn(addr, false)
	if bc == nil {
		return false
	} else {
		bc.PushBack(r)
		s.putBackendConn(bc)
		return true
	}
}

func (s *Router) getBackendConn(addr string, create bool) *SharedBackendConn {
	if bc := s.pool[addr]; bc != nil {
		return bc.Retain()
	} else if create {
		bc := NewSharedBackendConn(addr, s.config)
		s.pool[addr] = bc
		return bc
	} else {
		return nil
	}
}

func (s *Router) putBackendConn(bc *SharedBackendConn) {
	if bc != nil && bc.Release() {
		delete(s.pool, bc.Addr())
	}
}

func (s *Router) fillSlot(m *models.Slot) {
	slot := &s.slots[m.Id]
	slot.blockAndWait()

	s.putBackendConn(slot.backend.bc)
	slot.backend.bc = nil
	slot.backend.id = 0
	s.putBackendConn(slot.migrate.bc)
	slot.migrate.bc = nil
	slot.migrate.id = 0
	for i := range slot.replicaGroups {
		for _, bc := range slot.replicaGroups[i] {
			s.putBackendConn(bc)
		}
	}
	slot.replicaGroups = nil

	if addr := m.BackendAddr; len(addr) != 0 {
		slot.backend.bc = s.getBackendConn(addr, true)
		slot.backend.id = m.BackendAddrId
	}
	if from := m.MigrateFrom; len(from) != 0 {
		slot.migrate.bc = s.getBackendConn(from, true)
		slot.migrate.id = m.MigrateFromId
	}
	for i := range m.ReplicaGroups {
		var group []*SharedBackendConn
		for _, addr := range m.ReplicaGroups[i] {
			group = append(group, s.getBackendConn(addr, true))
		}
		slot.replicaGroups = append(slot.replicaGroups, group)
	}

	if !m.Locked {
		slot.unblock()
	}
	if !s.closed {
		if slot.migrate.bc != nil {
			log.Warnf("fill slot %04d, backend.addr = %s, migrate.from = %s, locked = %t",
				slot.id, slot.backend.bc.Addr(), slot.migrate.bc.Addr(), slot.lock.hold)
		} else {
			log.Warnf("fill slot %04d, backend.addr = %s, locked = %t",
				slot.id, slot.backend.bc.Addr(), slot.lock.hold)
		}
	}
}

func (s *Router) SwitchMasters(masters map[int]string) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.closed {
		return ErrClosedRouter
	}
	s.ha.masters = masters

	for i := range s.slots {
		s.trySwitchMaster(i)
	}
	return nil
}

func (s *Router) trySwitchMaster(id int) {
	if len(s.ha.masters) == 0 {
		return
	}
	m := s.slots[id].snapshot(false)

	var refill = false

	if addr := s.ha.masters[m.BackendAddrId]; len(addr) != 0 {
		if addr != m.BackendAddr {
			m.BackendAddr = addr
			refill = true
		}
	}
	if from := s.ha.masters[m.MigrateFromId]; len(from) != 0 {
		if from != m.MigrateFrom {
			m.MigrateFrom = from
			refill = true
		}
	}
	if !refill {
		return
	}
	log.Warnf("HA: fill slot %04d +switch-master", id)
	s.fillSlot(m)
}

func (s *Router) SetSentinels(servers []string) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.closed {
		return ErrClosedProxy
	}
	if s.ha.monitor != nil {
		s.ha.monitor.Cancel()
		s.ha.monitor = nil
	}
	log.Warnf("set sentinels = %v", servers)

	if len(servers) == 0 {
		s.ha.masters = nil
	} else {
		s.ha.monitor = redis.NewSentinel(s.config.ProductName)
		go func(p *redis.Sentinel) {
			for {
				timeout := time.Second * 5
				masters := p.Masters(s.GetGroupIds(), timeout, servers...)
				if p.IsCancelled() {
					return
				}
				s.SwitchMasters(masters)

				retryAt := time.Now().Add(time.Minute)
				if !p.Subscribe(time.Hour, servers...) {
					for time.Now().Before(retryAt) {
						if p.IsCancelled() {
							return
						}
						time.Sleep(time.Second)
					}
				}
				time.Sleep(time.Millisecond * 250)
			}
		}(s.ha.monitor)
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2018 Google LLC
//
// 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 fs implements a virtual filesystem layer.
//
// Specific filesystem implementations must implement the InodeOperations
// interface (inode.go).
//
// The MountNamespace (mounts.go) is used to create a collection of mounts in
// a filesystem rooted at a given Inode.
//
// MountSources (mount.go) form a tree, with each mount holding pointers to its
// parent and children.
//
// Dirents (dirents.go) wrap Inodes in a caching layer.
//
// When multiple locks are to be held at the same time, they should be acquired
// in the following order.
//
// Either:
//   File.mu
//     Locks in FileOperations implementations
//       goto Dirent-Locks
//
// Or:
//   MountNamespace.mu
//     goto Dirent-Locks
//
// Dirent-Locks:
//   renameMu
//     Dirent.dirMu
//       Dirent.mu
//         DirentCache.mu
//         Locks in InodeOperations implementations or overlayEntry
//         Inode.Watches.mu (see `Inotify` for other lock ordering)
//         MountSource.mu
//
// If multiple Dirent or MountSource locks must be taken, locks in the parent must be
// taken before locks in their children.
//
// If locks must be taken on multiple unrelated Dirents, renameMu must be taken
// first. See lockForRename.
package fs

import (
	"sync"

	"gvisor.googlesource.com/gvisor/pkg/log"
	"gvisor.googlesource.com/gvisor/pkg/sentry/context"
)

var (
	// workMu is used to synchronize pending asynchronous work. Async work
	// runs with the lock held for reading. AsyncBarrier will take the lock
	// for writing, thus ensuring that all Async work completes before
	// AsyncBarrier returns.
	workMu sync.RWMutex

	// asyncError is used to store up to one asynchronous execution error.
	asyncError = make(chan error, 1)
)

// AsyncBarrier waits for all outstanding asynchronous work to complete.
func AsyncBarrier() {
	workMu.Lock()
	workMu.Unlock()
}

// Async executes a function asynchronously.
//
// Async must not be called recursively.
func Async(f func()) {
	workMu.RLock()
	go func() { // S/R-SAFE: AsyncBarrier must be called.
		defer workMu.RUnlock() // Ensure RUnlock in case of panic.
		f()
	}()
}

// AsyncWithContext is just like Async, except that it calls the asynchronous
// function with the given context as argument. This function exists to avoid
// needing to allocate an extra function on the heap in a hot path.
func AsyncWithContext(ctx context.Context, f func(context.Context)) {
	workMu.RLock()
	go func() { // S/R-SAFE: AsyncBarrier must be called.
		defer workMu.RUnlock() // Ensure RUnlock in case of panic.
		f(ctx)
	}()
}

// AsyncErrorBarrier waits for all outstanding asynchronous work to complete, or
// the first async error to arrive. Other unfinished async executions will
// continue in the background. Other past and future async errors are ignored.
func AsyncErrorBarrier() error {
	wait := make(chan struct{}, 1)
	go func() { // S/R-SAFE: Does not touch persistent state.
		AsyncBarrier()
		wait <- struct{}{}
	}()
	select {
	case <-wait:
		select {
		case err := <-asyncError:
			return err
		default:
			return nil
		}
	case err := <-asyncError:
		return err
	}
}

// CatchError tries to capture the potential async error returned by the
// function. At most one async error will be captured globally so excessive
// errors will be dropped.
func CatchError(f func() error) func() {
	return func() {
		if err := f(); err != nil {
			select {
			case asyncError <- err:
			default:
				log.Warningf("excessive async error dropped: %v", err)
			}
		}
	}
}

// ErrSaveRejection indicates a failed save due to unsupported file system state
// such as dangling open fd, etc.
type ErrSaveRejection struct {
	// Err is the wrapped error.
	Err error
}

// Error returns a sensible description of the save rejection error.
func (e ErrSaveRejection) Error() string {
	return "save rejected due to unsupported file system state: " + e.Err.Error()
}

// ErrCorruption indicates a failed restore due to external file system state in
// corruption.
type ErrCorruption struct {
	// Err is the wrapped error.
	Err error
}

// Error returns a sensible description of the save rejection error.
func (e ErrCorruption) Error() string {
	return "restore failed due to external file system state in corruption: " + e.Err.Error()
}
<commit_msg>Fix doc typo<commit_after>// Copyright 2018 Google LLC
//
// 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 fs implements a virtual filesystem layer.
//
// Specific filesystem implementations must implement the InodeOperations
// interface (inode.go).
//
// The MountNamespace (mounts.go) is used to create a collection of mounts in
// a filesystem rooted at a given Inode.
//
// MountSources (mount.go) form a tree, with each mount holding pointers to its
// parent and children.
//
// Dirents (dirents.go) wrap Inodes in a caching layer.
//
// When multiple locks are to be held at the same time, they should be acquired
// in the following order.
//
// Either:
//   File.mu
//     Locks in FileOperations implementations
//       goto Dirent-Locks
//
// Or:
//   MountNamespace.mu
//     goto Dirent-Locks
//
// Dirent-Locks:
//   renameMu
//     Dirent.dirMu
//       Dirent.mu
//         DirentCache.mu
//         Locks in InodeOperations implementations or overlayEntry
//         Inode.Watches.mu (see `Inotify` for other lock ordering)
//         MountSource.mu
//
// If multiple Dirent or MountSource locks must be taken, locks in the parent must be
// taken before locks in their children.
//
// If locks must be taken on multiple unrelated Dirents, renameMu must be taken
// first. See lockForRename.
package fs

import (
	"sync"

	"gvisor.googlesource.com/gvisor/pkg/log"
	"gvisor.googlesource.com/gvisor/pkg/sentry/context"
)

var (
	// workMu is used to synchronize pending asynchronous work. Async work
	// runs with the lock held for reading. AsyncBarrier will take the lock
	// for writing, thus ensuring that all Async work completes before
	// AsyncBarrier returns.
	workMu sync.RWMutex

	// asyncError is used to store up to one asynchronous execution error.
	asyncError = make(chan error, 1)
)

// AsyncBarrier waits for all outstanding asynchronous work to complete.
func AsyncBarrier() {
	workMu.Lock()
	workMu.Unlock()
}

// Async executes a function asynchronously.
//
// Async must not be called recursively.
func Async(f func()) {
	workMu.RLock()
	go func() { // S/R-SAFE: AsyncBarrier must be called.
		defer workMu.RUnlock() // Ensure RUnlock in case of panic.
		f()
	}()
}

// AsyncWithContext is just like Async, except that it calls the asynchronous
// function with the given context as argument. This function exists to avoid
// needing to allocate an extra function on the heap in a hot path.
func AsyncWithContext(ctx context.Context, f func(context.Context)) {
	workMu.RLock()
	go func() { // S/R-SAFE: AsyncBarrier must be called.
		defer workMu.RUnlock() // Ensure RUnlock in case of panic.
		f(ctx)
	}()
}

// AsyncErrorBarrier waits for all outstanding asynchronous work to complete, or
// the first async error to arrive. Other unfinished async executions will
// continue in the background. Other past and future async errors are ignored.
func AsyncErrorBarrier() error {
	wait := make(chan struct{}, 1)
	go func() { // S/R-SAFE: Does not touch persistent state.
		AsyncBarrier()
		wait <- struct{}{}
	}()
	select {
	case <-wait:
		select {
		case err := <-asyncError:
			return err
		default:
			return nil
		}
	case err := <-asyncError:
		return err
	}
}

// CatchError tries to capture the potential async error returned by the
// function. At most one async error will be captured globally so excessive
// errors will be dropped.
func CatchError(f func() error) func() {
	return func() {
		if err := f(); err != nil {
			select {
			case asyncError <- err:
			default:
				log.Warningf("excessive async error dropped: %v", err)
			}
		}
	}
}

// ErrSaveRejection indicates a failed save due to unsupported file system state
// such as dangling open fd, etc.
type ErrSaveRejection struct {
	// Err is the wrapped error.
	Err error
}

// Error returns a sensible description of the save rejection error.
func (e ErrSaveRejection) Error() string {
	return "save rejected due to unsupported file system state: " + e.Err.Error()
}

// ErrCorruption indicates a failed restore due to external file system state in
// corruption.
type ErrCorruption struct {
	// Err is the wrapped error.
	Err error
}

// Error returns a sensible description of the restore error.
func (e ErrCorruption) Error() string {
	return "restore failed due to external file system state in corruption: " + e.Err.Error()
}
<|endoftext|>


<commit_before>package store

import (
	"crypto/tls"
	"net/http"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"
	api "github.com/hashicorp/consul/api"
)

const (
	// DefaultWatchWaitTime is how long we block for at a time to check if the
	// watched key has changed.  This affects the minimum time it takes to
	// cancel a watch.
	DefaultWatchWaitTime = 15 * time.Second
)

// Consul embeds the client and watches
type Consul struct {
	config           *api.Config
	client           *api.Client
	ephemeralTTL     time.Duration
	ephemeralSession string
	sessions         map[string]string
}

type consulLock struct {
	lock *api.Lock
}

// InitializeConsul creates a new Consul client given
// a list of endpoints and optional tls config
func InitializeConsul(endpoints []string, options *Config) (Store, error) {
	s := &Consul{}
	s.sessions = make(map[string]string)

	// Create Consul client
	config := api.DefaultConfig()
	s.config = config
	config.HttpClient = http.DefaultClient
	config.Address = endpoints[0]
	config.Scheme = "http"

	// Set options
	if options != nil {
		if options.TLS != nil {
			s.setTLS(options.TLS)
		}
		if options.ConnectionTimeout != 0 {
			s.setTimeout(options.ConnectionTimeout)
		}
		if options.EphemeralTTL != 0 {
			s.setEphemeralTTL(options.EphemeralTTL)
		}
	}

	// Creates a new client
	client, err := api.NewClient(config)
	if err != nil {
		log.Errorf("Couldn't initialize consul client..")
		return nil, err
	}
	s.client = client

	return s, nil
}

// SetTLS sets Consul TLS options
func (s *Consul) setTLS(tls *tls.Config) {
	s.config.HttpClient.Transport = &http.Transport{
		TLSClientConfig: tls,
	}
	s.config.Scheme = "https"
}

// SetTimeout sets the timout for connecting to Consul
func (s *Consul) setTimeout(time time.Duration) {
	s.config.WaitTime = time
}

// SetEphemeralTTL sets the ttl for ephemeral nodes
func (s *Consul) setEphemeralTTL(time time.Duration) {
	s.ephemeralTTL = time
}

// Normalize the key for usage in Consul
func (s *Consul) normalize(key string) string {
	key = normalize(key)
	return strings.TrimPrefix(key, "/")
}

// Get the value at "key", returns the last modified index
// to use in conjunction to CAS calls
func (s *Consul) Get(key string) (*KVPair, error) {
	pair, meta, err := s.client.KV().Get(s.normalize(key), nil)
	if err != nil {
		return nil, err
	}
	if pair == nil {
		return nil, ErrKeyNotFound
	}
	return &KVPair{pair.Key, pair.Value, meta.LastIndex}, nil
}

// Put a value at "key"
func (s *Consul) Put(key string, value []byte, opts *WriteOptions) error {

	key = s.normalize(key)

	p := &api.KVPair{
		Key:   key,
		Value: value,
	}

	if opts != nil && opts.Ephemeral {
		if _, ok := s.sessions[key]; !ok {
			entry := &api.SessionEntry{
				Behavior: api.SessionBehaviorDelete,
				TTL:      s.ephemeralTTL.String(),
			}

			// Create global ephemeral keys session
			session, _, err := s.client.Session().Create(entry, nil)
			if err != nil {
				return err
			}

			// Create lock option with the
			// EphemeralSession
			lockOpts := &api.LockOptions{
				Key:     key,
				Session: session,
			}

			// Lock and ignore if lock is held
			// It's just a placeholder for the
			// ephemeral behavior
			lock, _ := s.client.LockOpts(lockOpts)
			if lock != nil {
				lock.Lock(nil)
			}

			s.sessions[key] = session
			p.Session = session

			// Renew the session periodically if heartbeat set
			if opts.Heartbeat != 0 {
				go func() {
					ticker := time.NewTicker(opts.Heartbeat)
					for {
						select {
						case <-ticker.C:
							_, _, err := s.client.Session().Renew(p.Session, nil)
							if err != nil {
								delete(s.sessions, key)
								return
							}
						}
					}
				}()
			}
		}
	}

	_, err := s.client.KV().Put(p, nil)
	return err
}

// Delete a value at "key"
func (s *Consul) Delete(key string) error {
	_, err := s.client.KV().Delete(s.normalize(key), nil)
	return err
}

// Exists checks that the key exists inside the store
func (s *Consul) Exists(key string) (bool, error) {
	_, err := s.Get(key)
	if err != nil && err == ErrKeyNotFound {
		return false, err
	}
	return true, nil
}

// List the content of a given prefix
func (s *Consul) List(prefix string) ([]*KVPair, error) {
	pairs, _, err := s.client.KV().List(s.normalize(prefix), nil)
	if err != nil {
		return nil, err
	}
	if len(pairs) == 0 {
		return nil, ErrKeyNotFound
	}
	kv := []*KVPair{}
	for _, pair := range pairs {
		if pair.Key == prefix {
			continue
		}
		kv = append(kv, &KVPair{pair.Key, pair.Value, pair.ModifyIndex})
	}
	return kv, nil
}

// DeleteTree deletes a range of keys based on prefix
func (s *Consul) DeleteTree(prefix string) error {
	_, err := s.client.KV().DeleteTree(s.normalize(prefix), nil)
	return err
}

// Watch changes on a key.
// Returns a channel that will receive changes or an error.
// Upon creating a watch, the current value will be sent to the channel.
// Providing a non-nil stopCh can be used to stop watching.
func (s *Consul) Watch(key string, stopCh <-chan struct{}) (<-chan *KVPair, error) {
	key = s.normalize(key)
	kv := s.client.KV()
	watchCh := make(chan *KVPair)

	go func() {
		defer close(watchCh)

		// Use a wait time in order to check if we should quit from time to
		// time.
		opts := &api.QueryOptions{WaitTime: DefaultWatchWaitTime}
		for {
			// Check if we should quit
			select {
			case <-stopCh:
				return
			default:
			}
			pair, meta, err := kv.Get(key, opts)
			if err != nil {
				log.Errorf("consul: %v", err)
				return
			}
			// If LastIndex didn't change then it means `Get` returned because
			// of the WaitTime and the key didn't change.
			if opts.WaitIndex == meta.LastIndex {
				continue
			}
			opts.WaitIndex = meta.LastIndex
			// FIXME: What happens when a key is deleted?
			if pair != nil {
				watchCh <- &KVPair{pair.Key, pair.Value, pair.ModifyIndex}
			}
		}
	}()

	return watchCh, nil
}

// WatchTree watches changes on a "directory"
// Returns a channel that will receive changes or an error.
// Upon creating a watch, the current value will be sent to the channel.
// Providing a non-nil stopCh can be used to stop watching.
func (s *Consul) WatchTree(prefix string, stopCh <-chan struct{}) (<-chan []*KVPair, error) {
	prefix = s.normalize(prefix)
	kv := s.client.KV()
	watchCh := make(chan []*KVPair)

	go func() {
		defer close(watchCh)

		// Use a wait time in order to check if we should quit from time to
		// time.
		opts := &api.QueryOptions{WaitTime: DefaultWatchWaitTime}
		for {
			// Check if we should quit
			select {
			case <-stopCh:
				return
			default:
			}

			pairs, meta, err := kv.List(prefix, opts)
			if err != nil {
				log.Errorf("consul: %v", err)
				return
			}
			// If LastIndex didn't change then it means `Get` returned because
			// of the WaitTime and the key didn't change.
			if opts.WaitIndex == meta.LastIndex {
				continue
			}
			opts.WaitIndex = meta.LastIndex
			kv := []*KVPair{}
			for _, pair := range pairs {
				if pair.Key == prefix {
					continue
				}
				kv = append(kv, &KVPair{pair.Key, pair.Value, pair.ModifyIndex})
			}
			watchCh <- kv
		}
	}()

	return watchCh, nil
}

// CreateLock returns a handle to a lock struct which can be used
// to acquire and release the mutex.
func (s *Consul) CreateLock(key string, value []byte) (Locker, error) {
	l, err := s.client.LockOpts(&api.LockOptions{
		Key:   s.normalize(key),
		Value: value,
	})
	if err != nil {
		return nil, err
	}
	return &consulLock{lock: l}, nil
}

// Lock attempts to acquire the lock and blocks while doing so.
// Returns a channel that is closed if our lock is lost or an error.
func (l *consulLock) Lock() (<-chan struct{}, error) {
	return l.lock.Lock(nil)
}

// Unlock released the lock. It is an error to call this
// if the lock is not currently held.
func (l *consulLock) Unlock() error {
	return l.lock.Unlock()
}

// AtomicPut put a value at "key" if the key has not been
// modified in the meantime, throws an error if this is the case
func (s *Consul) AtomicPut(key string, value []byte, previous *KVPair, options *WriteOptions) (bool, error) {
	p := &api.KVPair{Key: s.normalize(key), Value: value, ModifyIndex: previous.LastIndex}
	if work, _, err := s.client.KV().CAS(p, nil); err != nil {
		return false, err
	} else if !work {
		return false, ErrKeyModified
	}
	return true, nil
}

// AtomicDelete deletes a value at "key" if the key has not
// been modified in the meantime, throws an error if this is the case
func (s *Consul) AtomicDelete(key string, previous *KVPair) (bool, error) {
	p := &api.KVPair{Key: s.normalize(key), ModifyIndex: previous.LastIndex}
	if work, _, err := s.client.KV().DeleteCAS(p, nil); err != nil {
		return false, err
	} else if !work {
		return false, ErrKeyModified
	}
	return true, nil
}
<commit_msg>remove the periodic session renewal in Consul until we figure out what to do with etcd..<commit_after>package store

import (
	"crypto/tls"
	"net/http"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"
	api "github.com/hashicorp/consul/api"
)

const (
	// DefaultWatchWaitTime is how long we block for at a time to check if the
	// watched key has changed.  This affects the minimum time it takes to
	// cancel a watch.
	DefaultWatchWaitTime = 15 * time.Second
)

// Consul embeds the client and watches
type Consul struct {
	config           *api.Config
	client           *api.Client
	ephemeralTTL     time.Duration
	ephemeralSession string
	sessions         map[string]string
}

type consulLock struct {
	lock *api.Lock
}

// InitializeConsul creates a new Consul client given
// a list of endpoints and optional tls config
func InitializeConsul(endpoints []string, options *Config) (Store, error) {
	s := &Consul{}
	s.sessions = make(map[string]string)

	// Create Consul client
	config := api.DefaultConfig()
	s.config = config
	config.HttpClient = http.DefaultClient
	config.Address = endpoints[0]
	config.Scheme = "http"

	// Set options
	if options != nil {
		if options.TLS != nil {
			s.setTLS(options.TLS)
		}
		if options.ConnectionTimeout != 0 {
			s.setTimeout(options.ConnectionTimeout)
		}
		if options.EphemeralTTL != 0 {
			s.setEphemeralTTL(options.EphemeralTTL)
		}
	}

	// Creates a new client
	client, err := api.NewClient(config)
	if err != nil {
		log.Errorf("Couldn't initialize consul client..")
		return nil, err
	}
	s.client = client

	return s, nil
}

// SetTLS sets Consul TLS options
func (s *Consul) setTLS(tls *tls.Config) {
	s.config.HttpClient.Transport = &http.Transport{
		TLSClientConfig: tls,
	}
	s.config.Scheme = "https"
}

// SetTimeout sets the timout for connecting to Consul
func (s *Consul) setTimeout(time time.Duration) {
	s.config.WaitTime = time
}

// SetEphemeralTTL sets the ttl for ephemeral nodes
func (s *Consul) setEphemeralTTL(time time.Duration) {
	s.ephemeralTTL = time
}

// Normalize the key for usage in Consul
func (s *Consul) normalize(key string) string {
	key = normalize(key)
	return strings.TrimPrefix(key, "/")
}

// Get the value at "key", returns the last modified index
// to use in conjunction to CAS calls
func (s *Consul) Get(key string) (*KVPair, error) {
	pair, meta, err := s.client.KV().Get(s.normalize(key), nil)
	if err != nil {
		return nil, err
	}
	if pair == nil {
		return nil, ErrKeyNotFound
	}
	return &KVPair{pair.Key, pair.Value, meta.LastIndex}, nil
}

// Put a value at "key"
func (s *Consul) Put(key string, value []byte, opts *WriteOptions) error {

	key = s.normalize(key)

	p := &api.KVPair{
		Key:   key,
		Value: value,
	}

	if opts != nil && opts.Ephemeral {
		if _, ok := s.sessions[key]; !ok {
			entry := &api.SessionEntry{
				Behavior: api.SessionBehaviorDelete,
				TTL:      s.ephemeralTTL.String(),
			}

			// Create global ephemeral keys session
			session, _, err := s.client.Session().Create(entry, nil)
			if err != nil {
				return err
			}

			// Create lock option with the
			// EphemeralSession
			lockOpts := &api.LockOptions{
				Key:     key,
				Session: session,
			}

			// Lock and ignore if lock is held
			// It's just a placeholder for the
			// ephemeral behavior
			lock, _ := s.client.LockOpts(lockOpts)
			if lock != nil {
				lock.Lock(nil)
			}

			s.sessions[key] = session
			p.Session = session
		}

		// Renew the session
		_, _, err := s.client.Session().Renew(p.Session, nil)
		if err != nil {
			delete(s.sessions, key)
			return err
		}
	}

	_, err := s.client.KV().Put(p, nil)
	return err
}

// Delete a value at "key"
func (s *Consul) Delete(key string) error {
	_, err := s.client.KV().Delete(s.normalize(key), nil)
	return err
}

// Exists checks that the key exists inside the store
func (s *Consul) Exists(key string) (bool, error) {
	_, err := s.Get(key)
	if err != nil && err == ErrKeyNotFound {
		return false, err
	}
	return true, nil
}

// List the content of a given prefix
func (s *Consul) List(prefix string) ([]*KVPair, error) {
	pairs, _, err := s.client.KV().List(s.normalize(prefix), nil)
	if err != nil {
		return nil, err
	}
	if len(pairs) == 0 {
		return nil, ErrKeyNotFound
	}
	kv := []*KVPair{}
	for _, pair := range pairs {
		if pair.Key == prefix {
			continue
		}
		kv = append(kv, &KVPair{pair.Key, pair.Value, pair.ModifyIndex})
	}
	return kv, nil
}

// DeleteTree deletes a range of keys based on prefix
func (s *Consul) DeleteTree(prefix string) error {
	_, err := s.client.KV().DeleteTree(s.normalize(prefix), nil)
	return err
}

// Watch changes on a key.
// Returns a channel that will receive changes or an error.
// Upon creating a watch, the current value will be sent to the channel.
// Providing a non-nil stopCh can be used to stop watching.
func (s *Consul) Watch(key string, stopCh <-chan struct{}) (<-chan *KVPair, error) {
	key = s.normalize(key)
	kv := s.client.KV()
	watchCh := make(chan *KVPair)

	go func() {
		defer close(watchCh)

		// Use a wait time in order to check if we should quit from time to
		// time.
		opts := &api.QueryOptions{WaitTime: DefaultWatchWaitTime}
		for {
			// Check if we should quit
			select {
			case <-stopCh:
				return
			default:
			}
			pair, meta, err := kv.Get(key, opts)
			if err != nil {
				log.Errorf("consul: %v", err)
				return
			}
			// If LastIndex didn't change then it means `Get` returned because
			// of the WaitTime and the key didn't change.
			if opts.WaitIndex == meta.LastIndex {
				continue
			}
			opts.WaitIndex = meta.LastIndex
			// FIXME: What happens when a key is deleted?
			if pair != nil {
				watchCh <- &KVPair{pair.Key, pair.Value, pair.ModifyIndex}
			}
		}
	}()

	return watchCh, nil
}

// WatchTree watches changes on a "directory"
// Returns a channel that will receive changes or an error.
// Upon creating a watch, the current value will be sent to the channel.
// Providing a non-nil stopCh can be used to stop watching.
func (s *Consul) WatchTree(prefix string, stopCh <-chan struct{}) (<-chan []*KVPair, error) {
	prefix = s.normalize(prefix)
	kv := s.client.KV()
	watchCh := make(chan []*KVPair)

	go func() {
		defer close(watchCh)

		// Use a wait time in order to check if we should quit from time to
		// time.
		opts := &api.QueryOptions{WaitTime: DefaultWatchWaitTime}
		for {
			// Check if we should quit
			select {
			case <-stopCh:
				return
			default:
			}

			pairs, meta, err := kv.List(prefix, opts)
			if err != nil {
				log.Errorf("consul: %v", err)
				return
			}
			// If LastIndex didn't change then it means `Get` returned because
			// of the WaitTime and the key didn't change.
			if opts.WaitIndex == meta.LastIndex {
				continue
			}
			opts.WaitIndex = meta.LastIndex
			kv := []*KVPair{}
			for _, pair := range pairs {
				if pair.Key == prefix {
					continue
				}
				kv = append(kv, &KVPair{pair.Key, pair.Value, pair.ModifyIndex})
			}
			watchCh <- kv
		}
	}()

	return watchCh, nil
}

// CreateLock returns a handle to a lock struct which can be used
// to acquire and release the mutex.
func (s *Consul) CreateLock(key string, value []byte) (Locker, error) {
	l, err := s.client.LockOpts(&api.LockOptions{
		Key:   s.normalize(key),
		Value: value,
	})
	if err != nil {
		return nil, err
	}
	return &consulLock{lock: l}, nil
}

// Lock attempts to acquire the lock and blocks while doing so.
// Returns a channel that is closed if our lock is lost or an error.
func (l *consulLock) Lock() (<-chan struct{}, error) {
	return l.lock.Lock(nil)
}

// Unlock released the lock. It is an error to call this
// if the lock is not currently held.
func (l *consulLock) Unlock() error {
	return l.lock.Unlock()
}

// AtomicPut put a value at "key" if the key has not been
// modified in the meantime, throws an error if this is the case
func (s *Consul) AtomicPut(key string, value []byte, previous *KVPair, options *WriteOptions) (bool, error) {
	p := &api.KVPair{Key: s.normalize(key), Value: value, ModifyIndex: previous.LastIndex}
	if work, _, err := s.client.KV().CAS(p, nil); err != nil {
		return false, err
	} else if !work {
		return false, ErrKeyModified
	}
	return true, nil
}

// AtomicDelete deletes a value at "key" if the key has not
// been modified in the meantime, throws an error if this is the case
func (s *Consul) AtomicDelete(key string, previous *KVPair) (bool, error) {
	p := &api.KVPair{Key: s.normalize(key), ModifyIndex: previous.LastIndex}
	if work, _, err := s.client.KV().DeleteCAS(p, nil); err != nil {
		return false, err
	} else if !work {
		return false, ErrKeyModified
	}
	return true, nil
}
<|endoftext|>


<commit_before>// +build !solaris,!windows

package term

import (
	"unsafe"

	"golang.org/x/sys/unix"
)

// GetWinsize returns the window size based on the specified file descriptor.
func GetWinsize(fd uintptr) (*Winsize, error) {
	ws := &Winsize{}
	_, _, err := unix.Syscall(unix.SYS_IOCTL, fd, uintptr(unix.TIOCGWINSZ), uintptr(unsafe.Pointer(ws)))
	// Skipp errno = 0
	if err == 0 {
		return ws, nil
	}
	return ws, err
}

// SetWinsize tries to set the specified window size for the specified file descriptor.
func SetWinsize(fd uintptr, ws *Winsize) error {
	_, _, err := unix.Syscall(unix.SYS_IOCTL, fd, uintptr(unix.TIOCSWINSZ), uintptr(unsafe.Pointer(ws)))
	// Skipp errno = 0
	if err == 0 {
		return nil
	}
	return err
}
<commit_msg>pkg/term: use IoctlGetWinsize/IoctlSetWinsize from golang.org/x/sys/unix<commit_after>// +build !solaris,!windows

package term

import (
	"golang.org/x/sys/unix"
)

// GetWinsize returns the window size based on the specified file descriptor.
func GetWinsize(fd uintptr) (*Winsize, error) {
	uws, err := unix.IoctlGetWinsize(int(fd), unix.TIOCGWINSZ)
	ws := &Winsize{Height: uws.Row, Width: uws.Col, x: uws.Xpixel, y: uws.Ypixel}
	return ws, err
}

// SetWinsize tries to set the specified window size for the specified file descriptor.
func SetWinsize(fd uintptr, ws *Winsize) error {
	uws := &unix.Winsize{Row: ws.Height, Col: ws.Width, Xpixel: ws.x, Ypixel: ws.y}
	return unix.IoctlSetWinsize(int(fd), unix.TIOCSWINSZ, uws)
}
<|endoftext|>


<commit_before>/*
Copyright 2014 Google Inc. All rights reserved.

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 version

// Base version information.
//
// This is the fallback data used when version information from git is not
// provided via go ldflags. It provides an approximation of the Kubernetes
// version for ad-hoc builds (e.g. `go build`) that cannot get the version
// information from git.
//
// The "-dev" suffix in the version info indicates that fact, and it means the
// current build is from a version greater that version. For example, v0.7-dev
// means version > 0.7 and < 0.8. (There's exceptions to this rule, see
// docs/releasing.md for more details.)
//
// When releasing a new Kubernetes version, this file should be updated to
// reflect the new version, and then a git annotated tag (using format vX.Y
// where X == Major version and Y == Minor version) should be created to point
// to the commit that updates pkg/version/base.go

var (
	// TODO: Deprecate gitMajor and gitMinor, use only gitVersion instead.
	gitMajor     string = "0"              // major version, always numeric
	gitMinor     string = "16.0"           // minor version, numeric possibly followed by "+"
	gitVersion   string = "v0.16.0"        // version from git, output of $(git describe)
	gitCommit    string = ""               // sha1 from git, output of $(git rev-parse HEAD)
	gitTreeState string = "not a git tree" // state of git tree, either "clean" or "dirty"
)
<commit_msg>Kubernetes version v0.16.0-dev<commit_after>/*
Copyright 2014 Google Inc. All rights reserved.

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 version

// Base version information.
//
// This is the fallback data used when version information from git is not
// provided via go ldflags. It provides an approximation of the Kubernetes
// version for ad-hoc builds (e.g. `go build`) that cannot get the version
// information from git.
//
// The "-dev" suffix in the version info indicates that fact, and it means the
// current build is from a version greater that version. For example, v0.7-dev
// means version > 0.7 and < 0.8. (There's exceptions to this rule, see
// docs/releasing.md for more details.)
//
// When releasing a new Kubernetes version, this file should be updated to
// reflect the new version, and then a git annotated tag (using format vX.Y
// where X == Major version and Y == Minor version) should be created to point
// to the commit that updates pkg/version/base.go

var (
	// TODO: Deprecate gitMajor and gitMinor, use only gitVersion instead.
	gitMajor     string = "0"              // major version, always numeric
	gitMinor     string = "16.0+"          // minor version, numeric possibly followed by "+"
	gitVersion   string = "v0.16.0-dev"    // version from git, output of $(git describe)
	gitCommit    string = ""               // sha1 from git, output of $(git rev-parse HEAD)
	gitTreeState string = "not a git tree" // state of git tree, either "clean" or "dirty"
)
<|endoftext|>


<commit_before>package web

import (
	"context"
	"fmt"
	"math/rand"
	"net/http"
	"psychic-rat/auth0"
	"psychic-rat/log"
	"psychic-rat/sess"
	"psychic-rat/types"
	"psychic-rat/web/admin"
	"psychic-rat/web/dispatch"
	"psychic-rat/web/pub"
)

const (
	HomePage      = "/"
	SignInPage    = "/signin"
	SignOutPage   = "/signout"
	PledgePage    = "/pledge"
	ThanksPage    = "/thanks"
	NewItem       = "/newitem"
	AdminNewItems = "/admin/newitems"
	Callback      = "/callback"
)

type statusRecorder struct {
	http.ResponseWriter
	status int
}

var (
	uriHandlers = []dispatch.URIHandler{
		{HomePage, pub.HomePageHandler},
		{SignInPage, pub.SignInPageHandler},
		{SignOutPage, pub.SignOutPageHandler},
		{PledgePage, pub.PledgePageHandler},
		{NewItem, pub.NewItemHandler},
		{ThanksPage, pub.ThanksPageHandler},
		{Callback, auth0.CallbackHandler},
		{AdminNewItems, admin.AdminItemHandler},
	}

	flags struct {
		enableAuth0, sqldb bool
	}
)

func Handler() http.Handler {
	hmux := http.NewServeMux()
	handlerForDirs(hmux, "css", "js", "images")
	for _, h := range uriHandlers {
		hmux.HandleFunc(h.URI, addContextValues(logRequest(h.Handler)))
	}
	return hmux
}

func addContextValues(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		requestID := rand.Int63()
		user, _ := sess.NewSessionStore(r).Get()
		if user == nil {
			user = &types.User{ID: "<none>"}
		}
		ctx := context.WithValue(r.Context(), "rid", requestID)
		ctx = context.WithValue(ctx, "uid", user.ID)
		r = r.WithContext(ctx)
		next(w, r)
	}
}

func logRequest(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		request := fmt.Sprintf("request: %s %s", r.Method, r.RequestURI)
		log.Logf(r, request)
		scw := &statusRecorder{w, 200}
		next(scw, r)
		log.Logf(r, "response: %d", scw.status)
	}
}

func handlerForDirs(mux *http.ServeMux, dir ...string) {
	for _, d := range dir {
		mux.Handle("/"+d+"/", http.StripPrefix("/"+d, http.FileServer(http.Dir("res/"+d+"/"))))
	}
}

func (s *statusRecorder) WriteHeader(c int) {
	s.status = c
	s.ResponseWriter.WriteHeader(c)
}
<commit_msg>Added request addr<commit_after>package web

import (
	"context"
	"fmt"
	"math/rand"
	"net/http"
	"psychic-rat/auth0"
	"psychic-rat/log"
	"psychic-rat/sess"
	"psychic-rat/types"
	"psychic-rat/web/admin"
	"psychic-rat/web/dispatch"
	"psychic-rat/web/pub"
)

const (
	HomePage      = "/"
	SignInPage    = "/signin"
	SignOutPage   = "/signout"
	PledgePage    = "/pledge"
	ThanksPage    = "/thanks"
	NewItem       = "/newitem"
	AdminNewItems = "/admin/newitems"
	Callback      = "/callback"
)

type statusRecorder struct {
	http.ResponseWriter
	status int
}

var (
	uriHandlers = []dispatch.URIHandler{
		{HomePage, pub.HomePageHandler},
		{SignInPage, pub.SignInPageHandler},
		{SignOutPage, pub.SignOutPageHandler},
		{PledgePage, pub.PledgePageHandler},
		{NewItem, pub.NewItemHandler},
		{ThanksPage, pub.ThanksPageHandler},
		{Callback, auth0.CallbackHandler},
		{AdminNewItems, admin.AdminItemHandler},
	}

	flags struct {
		enableAuth0, sqldb bool
	}
)

func Handler() http.Handler {
	hmux := http.NewServeMux()
	handlerForDirs(hmux, "css", "js", "images")
	for _, h := range uriHandlers {
		hmux.HandleFunc(h.URI, addContextValues(logRequest(h.Handler)))
	}
	return hmux
}

func addContextValues(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		requestID := rand.Int63()
		user, _ := sess.NewSessionStore(r).Get()
		if user == nil {
			user = &types.User{ID: "<none>"}
		}
		ctx := context.WithValue(r.Context(), "rid", requestID)
		ctx = context.WithValue(ctx, "uid", user.ID)
		r = r.WithContext(ctx)
		next(w, r)
	}
}

func logRequest(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		request := fmt.Sprintf("source: %s request: %s %s", r.RemoteAddr, r.Method, r.RequestURI)
		log.Logf(r, request)
		scw := &statusRecorder{w, 200}
		next(scw, r)
		log.Logf(r, "response: %d", scw.status)
	}
}

func handlerForDirs(mux *http.ServeMux, dir ...string) {
	for _, d := range dir {
		mux.Handle("/"+d+"/", http.StripPrefix("/"+d, http.FileServer(http.Dir("res/"+d+"/"))))
	}
}

func (s *statusRecorder) WriteHeader(c int) {
	s.status = c
	s.ResponseWriter.WriteHeader(c)
}
<|endoftext|>


<commit_before>package playtak

import (
	"errors"
	"strings"
)

type Commands struct {
	Client
}

func (c *Commands) SendClient(name string) {
	c.SendCommand("client", name)
}

func (c *Commands) Login(user, pass string) error {
	for line := range c.Recv() {
		if strings.HasPrefix(line, "Login ") {
			break
		}
	}
	if pass == "" {
		c.SendCommand("Login", user)
	} else {
		c.SendCommand("Login", user, pass)
	}
	for line := range c.Recv() {
		if line == "Login or Register" {
			return errors.New("bad password")
		}
		if line == "You're already logged in" {
			return errors.New("user is already logged in")
		}
		if strings.HasPrefix(line, "Welcome ") {
			return nil
		}
	}
	return errors.New("login failed")
}

func (c *Commands) LoginGuest() error {
	return c.Login("Guest", "")
}

func (c *Commands) Shout(room, msg string) {
	if room == "" {
		c.SendCommand("Shout", msg)
	} else {
		c.SendCommand("ShoutRoom", room, msg)
	}
}

func (c *Commands) Tell(who, msg string) {
	c.SendCommand("Tell", who, msg)
}
<commit_msg>fix login handshake<commit_after>package playtak

import (
	"errors"
	"strings"
)

type Commands struct {
	Client
}

func (c *Commands) SendClient(name string) {
	c.SendCommand("client", name)
}

func (c *Commands) Login(user, pass string) error {
	for line := range c.Recv() {
		if strings.HasPrefix(line, "Login ") {
			break
		}
	}
	if pass == "" {
		c.SendCommand("Login", user)
	} else {
		c.SendCommand("Login", user, pass)
	}
	for line := range c.Recv() {
		if line == "Authentication failure" {
			return errors.New("bad username or password")
		}
		if strings.HasPrefix(line, "Welcome ") {
			return nil
		}
	}
	return errors.New("login failed")
}

func (c *Commands) LoginGuest() error {
	return c.Login("Guest", "")
}

func (c *Commands) Shout(room, msg string) {
	if room == "" {
		c.SendCommand("Shout", msg)
	} else {
		c.SendCommand("ShoutRoom", room, msg)
	}
}

func (c *Commands) Tell(who, msg string) {
	c.SendCommand("Tell", who, msg)
}
<|endoftext|>