<commit_before>package water

import (
	"html/template"
	"net/http"
	"strconv"
)

func (ctx *Context) Cookie(name string) string {
	cookie, err := ctx.Request.Cookie(name)
	if err != nil {
		return ""
	}

	return cookie.Value
}

func (ctx *Context) CookieEscape(name string) string {
	return template.HTMLEscapeString(ctx.Cookie(name))
}

func (ctx *Context) CookieBool(name string) bool {
	v, _ := strconv.ParseBool(ctx.Cookie(name))
	return v
}

func (ctx *Context) CookieInt(name string) int {
	v, _ := strconv.Atoi(ctx.Cookie(name))
	return v
}

func (ctx *Context) CookieInt64(name string) int64 {
	v, _ := strconv.ParseInt(ctx.Cookie(name), 10, 64)
	return v
}

func (ctx *Context) CookieUint(name string) uint {
	v, _ := strconv.ParseUint(ctx.Cookie(name), 10, 64)
	return uint(v)
}

func (ctx *Context) CookieUint64(name string) uint64 {
	v, _ := strconv.ParseUint(ctx.Cookie(name), 10, 64)
	return v
}

func (ctx *Context) CookieFloat64(name string) float64 {
	v, _ := strconv.ParseFloat(ctx.Cookie(name), 64)
	return v
}

// name/value escape before SetCookie() if necessary
// others... : MaxAge, Path, Domain, Secure, HttpOnly.
func (ctx *Context) SetCookie(name string, value string, others ...interface{}) {
	cookie := http.Cookie{}
	cookie.Name = name
	cookie.Value = value

	if len(others) > 0 {
		switch v := others[0].(type) {
		case int:
			cookie.MaxAge = v
		case int32:
			cookie.MaxAge = int(v)
		case int64:
			cookie.MaxAge = int(v)
		}
	}

	cookie.Path = "/"
	if len(others) > 1 {
		if v, ok := others[1].(string); ok && len(v) > 0 {
			cookie.Path = v
		}
	}

	if len(others) > 2 {
		if v, ok := others[2].(string); ok && len(v) > 0 {
			cookie.Domain = v
		}
	}

	if len(others) > 3 {
		switch v := others[3].(type) {
		case bool:
			cookie.Secure = v
		default:
			if others[3] != nil {
				cookie.Secure = true
			}
		}
	}

	if len(others) > 4 {
		if v, ok := others[4].(bool); ok && v {
			cookie.HttpOnly = true
		}
	}

	ctx.ResponseWriter.Header().Add("Set-Cookie", cookie.String())
}
<commit_msg>+ cookie support SameSite<commit_after>package water

import (
	"html/template"
	"net/http"
	"strconv"
)

func (ctx *Context) Cookie(name string) string {
	cookie, err := ctx.Request.Cookie(name)
	if err != nil {
		return ""
	}

	return cookie.Value
}

func (ctx *Context) CookieEscape(name string) string {
	return template.HTMLEscapeString(ctx.Cookie(name))
}

func (ctx *Context) CookieBool(name string) bool {
	v, _ := strconv.ParseBool(ctx.Cookie(name))
	return v
}

func (ctx *Context) CookieInt(name string) int {
	v, _ := strconv.Atoi(ctx.Cookie(name))
	return v
}

func (ctx *Context) CookieInt64(name string) int64 {
	v, _ := strconv.ParseInt(ctx.Cookie(name), 10, 64)
	return v
}

func (ctx *Context) CookieUint(name string) uint {
	v, _ := strconv.ParseUint(ctx.Cookie(name), 10, 64)
	return uint(v)
}

func (ctx *Context) CookieUint64(name string) uint64 {
	v, _ := strconv.ParseUint(ctx.Cookie(name), 10, 64)
	return v
}

func (ctx *Context) CookieFloat64(name string) float64 {
	v, _ := strconv.ParseFloat(ctx.Cookie(name), 64)
	return v
}

// name/value escape before SetCookie() if necessary
// others... : MaxAge, Path, Domain, Secure, HttpOnly, SameSite.
func (ctx *Context) SetCookie(name string, value string, others ...interface{}) {
	cookie := http.Cookie{}
	cookie.Name = name
	cookie.Value = value

	if len(others) > 0 {
		switch v := others[0].(type) {
		case int:
			cookie.MaxAge = v
		case int32:
			cookie.MaxAge = int(v)
		case int64:
			cookie.MaxAge = int(v)
		}
	}

	cookie.Path = "/"
	if len(others) > 1 {
		if v, ok := others[1].(string); ok && len(v) > 0 {
			cookie.Path = v
		}
	}

	if len(others) > 2 {
		if v, ok := others[2].(string); ok && len(v) > 0 {
			cookie.Domain = v
		}
	}

	if len(others) > 3 {
		switch v := others[3].(type) {
		case bool:
			cookie.Secure = v
		default:
			if others[3] != nil {
				cookie.Secure = true
			}
		}
	}

	if len(others) > 4 {
		if v, ok := others[4].(bool); ok && v {
			cookie.HttpOnly = true
		}
	}

	if len(others) > 5 {
		switch v := others[5].(type) {
		case bool:
			if v {
				cookie.SameSite = http.SameSiteStrictMode
			} else {
				cookie.SameSite = http.SameSiteLaxMode
			}
		default:
			if others[5] != nil {
				cookie.SameSite = http.SameSiteLaxMode
			}
		}
	}

	ctx.ResponseWriter.Header().Add("Set-Cookie", cookie.String())
}
<|endoftext|>


<commit_before>package sol

import (
	"fmt"
	"strings"

	"github.com/aodin/sol/dialect"
)

// For the Postgres implementation:
// http://www.postgresql.org/docs/9.4/static/sql-createtable.html

// CreateStmt is the internal representation of an CREATE TABLE statement.
type CreateStmt struct {
	table *TableElem
}

// String outputs the parameter-less CREATE TABLE statement in a neutral
// dialect.
func (stmt CreateStmt) String() string {
	c, _ := stmt.Compile(&defaultDialect{}, Params())
	return c
}

// Compile outputs the CREATE TABLE statement using the given dialect and
// parameters. An error may be returned because of a pre-existing error or
// because an error occurred during compilation.
func (stmt CreateStmt) Compile(d dialect.Dialect, p *Parameters) (string, error) {
	// Compiled elements
	compiled := make([]string, len(stmt.table.creates))

	var err error
	for i, create := range stmt.table.creates {
		if compiled[i], err = create.Create(d); err != nil {
			return "", err
		}
	}

	return fmt.Sprintf(
		"CREATE TABLE %s (\n  %s\n);",
		stmt.table.Name(),
		strings.Join(compiled, ",\n  "),
	), nil
}
<commit_msg>Added IF NOT EXISTS option to CREATE TABLE<commit_after>package sol

import (
	"fmt"
	"strings"

	"github.com/aodin/sol/dialect"
)

// For the Postgres implementation:
// http://www.postgresql.org/docs/9.4/static/sql-createtable.html

// CreateStmt is the internal representation of an CREATE TABLE statement.
type CreateStmt struct {
	table       *TableElem
	ifNotExists bool
}

// String outputs the parameter-less CREATE TABLE statement in a neutral
// dialect.
func (stmt CreateStmt) String() string {
	c, _ := stmt.Compile(&defaultDialect{}, Params())
	return c
}

func (stmt CreateStmt) IfNotExists() CreateStmt {
	stmt.ifNotExists = true
	return stmt
}

// Compile outputs the CREATE TABLE statement using the given dialect and
// parameters. An error may be returned because of a pre-existing error or
// because an error occurred during compilation.
func (stmt CreateStmt) Compile(d dialect.Dialect, p *Parameters) (string, error) {
	// Compiled elements
	compiled := make([]string, len(stmt.table.creates))

	var err error
	for i, create := range stmt.table.creates {
		if compiled[i], err = create.Create(d); err != nil {
			return "", err
		}
	}

	var name = fmt.Sprintf("CREATE TABLE %s", stmt.table.Name())
	if stmt.ifNotExists {
		name += " IF NOT EXISTS"
	}

	return fmt.Sprintf(
		"%s (\n  %s\n);", name, strings.Join(compiled, ",\n  "),
	), nil
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"io"

	pb "github.com/jonathanwei/fproxy/proto"

	"github.com/golang/glog"
	"github.com/golang/protobuf/proto"
)

func NewAEAD(key []byte) (cipher.AEAD, error) {
	c, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}

	aead, err := cipher.NewGCM(c)
	if err != nil {
		return nil, err
	}

	return aead, nil
}

func MustNewAEAD(key []byte) cipher.AEAD {
	a, err := NewAEAD(key)
	if err != nil {
		glog.Fatalf("Couldn't init AEAD: %v", err)
	}
	return a
}

// Encrypt a proto using an AEAD.
func EncryptProto(aead cipher.AEAD, msg proto.Message, additionalData []byte) string {
	plaintext := MustMarshalProto(msg)

	nonce := getNonce(aead.NonceSize())

	// Encrypt in-place.
	ciphertext := plaintext
	ciphertext = aead.Seal(ciphertext, nonce, plaintext, additionalData)

	outBytes := MustMarshalProto(&pb.EncryptedMessage{
		Nonce:      nonce,
		Ciphertext: ciphertext,
	})

	// Return base64'd, so that the output is ASCII-safe.
	return base64.RawStdEncoding.EncodeToString(outBytes)
}

// Decrypts a proto using an AEAD. Unmarshals the result into dst. The result
// should only be considered written if this function returns true.
func DecryptProto(aead cipher.AEAD, msg string, additionalData []byte, dst proto.Message) bool {
	msgBytes, err := base64.RawStdEncoding.DecodeString(msg)
	if err != nil {
		glog.V(2).Infof("Tried to decrypt proto with invalid base64: %v", err)
		return false
	}

	var msgProto pb.EncryptedMessage
	err = proto.Unmarshal(msgBytes, &msgProto)
	if err != nil {
		glog.V(2).Infof("Tried to decrypt proto with invalid pb.EncryptedMessage: %v", err)
		return false
	}

	plaintext, err := aead.Open(nil, msgProto.Nonce, msgProto.Ciphertext, additionalData)
	if err != nil {
		glog.V(2).Infof("Failed to decrypt data: %v", err)
		return false
	}

	err = proto.Unmarshal(plaintext, dst)
	if err != nil {
		glog.V(2).Infof("Failed to decrypt proto: %v", err)
		return false
	}

	return true
}

func getNonce(size int) []byte {
	nonce := make([]byte, size)
	_, err := io.ReadFull(rand.Reader, nonce)
	if err != nil {
		err := fmt.Errorf("Couldn't read from rand.Reader: %v", err)
		glog.Warning(err)
		panic(err)
	}
	return nonce
}

func MustMarshalProto(msg proto.Message) []byte {
	msgBytes, err := proto.Marshal(msg)
	if err != nil {
		err := fmt.Errorf("Couldn't marshal proto: %v; got err: %v", msg, err)
		glog.Warning(err)
		panic(err)
	}
	return msgBytes
}
<commit_msg>Decrypt in-place.<commit_after>package main

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"io"

	pb "github.com/jonathanwei/fproxy/proto"

	"github.com/golang/glog"
	"github.com/golang/protobuf/proto"
)

func NewAEAD(key []byte) (cipher.AEAD, error) {
	c, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}

	aead, err := cipher.NewGCM(c)
	if err != nil {
		return nil, err
	}

	return aead, nil
}

func MustNewAEAD(key []byte) cipher.AEAD {
	a, err := NewAEAD(key)
	if err != nil {
		glog.Fatalf("Couldn't init AEAD: %v", err)
	}
	return a
}

// Encrypt a proto using an AEAD.
func EncryptProto(aead cipher.AEAD, msg proto.Message, additionalData []byte) string {
	plaintext := MustMarshalProto(msg)

	nonce := getNonce(aead.NonceSize())

	// Encrypt in-place.
	ciphertext := plaintext
	ciphertext = aead.Seal(ciphertext, nonce, plaintext, additionalData)

	outBytes := MustMarshalProto(&pb.EncryptedMessage{
		Nonce:      nonce,
		Ciphertext: ciphertext,
	})

	// Return base64'd, so that the output is ASCII-safe.
	return base64.RawStdEncoding.EncodeToString(outBytes)
}

// Decrypts a proto using an AEAD. Unmarshals the result into dst. The result
// should only be considered written if this function returns true.
func DecryptProto(aead cipher.AEAD, msg string, additionalData []byte, dst proto.Message) bool {
	msgBytes, err := base64.RawStdEncoding.DecodeString(msg)
	if err != nil {
		glog.V(2).Infof("Tried to decrypt proto with invalid base64: %v", err)
		return false
	}

	var msgProto pb.EncryptedMessage
	err = proto.Unmarshal(msgBytes, &msgProto)
	if err != nil {
		glog.V(2).Infof("Tried to decrypt proto with invalid pb.EncryptedMessage: %v", err)
		return false
	}

	// Decrypt in-place.
	plaintext := msgProto.Ciphertext
	plaintext, err = aead.Open(plaintext, msgProto.Nonce, msgProto.Ciphertext, additionalData)
	if err != nil {
		glog.V(2).Infof("Failed to decrypt data: %v", err)
		return false
	}

	err = proto.Unmarshal(plaintext, dst)
	if err != nil {
		glog.V(2).Infof("Failed to decrypt proto: %v", err)
		return false
	}

	return true
}

func getNonce(size int) []byte {
	nonce := make([]byte, size)
	_, err := io.ReadFull(rand.Reader, nonce)
	if err != nil {
		err := fmt.Errorf("Couldn't read from rand.Reader: %v", err)
		glog.Warning(err)
		panic(err)
	}
	return nonce
}

func MustMarshalProto(msg proto.Message) []byte {
	msgBytes, err := proto.Marshal(msg)
	if err != nil {
		err := fmt.Errorf("Couldn't marshal proto: %v; got err: %v", msg, err)
		glog.Warning(err)
		panic(err)
	}
	return msgBytes
}
<|endoftext|>


<commit_before>package main

import (
	t "github.com/bytbox/termbox-go"
	"log"
)

var (
	width  int
	height int

	message string
)

var chActions = map[rune]func(){}

var keyActions = map[uint16]func(){}

func unknownAction() {
	panic("unrecognized keystroke")
}

func act(a func()) {
	defer func() {
		if er := recover(); er != nil {
			e, ok := er.(string)
			if ok {
				message = e
			} else {
				message = "unknown error"
			}
		}
	}()
	a()
}

func updateSize() {
	width = t.Width()
	height = t.Height()
}

func display() {
	t.Clear()
	i := 0
	for _, r := range message {
		t.ChangeCell(i, height-1, r, t.WHITE, t.BLACK)
		i++
	}
	t.Present()
}

func UIMain() {
	t.Init()
	defer func() {
		t.Shutdown()
	}()
	updateSize()
	e := t.Event{}
	for {
		display()
		e.Poll()
		switch e.Type {
		case t.EVENT_KEY:
			if e.Ch == 'q' {
				goto Exit
			}
			a, ok := chActions[e.Ch]
			if !ok {
				a, ok = keyActions[e.Key]
			}
			if ok {
				act(a)
			} else {
				act(unknownAction)
			}
		case t.EVENT_RESIZE:
			updateSize()
		default:
			log.Print("warning: unknown event type")
		}
	}

Exit:
}
<commit_msg>Using TermWriter<commit_after>package main

import (
	"fmt"
	t "github.com/bytbox/termbox-go"
	"log"
)

var (
	width  int
	height int

	message string
)

var chActions = map[rune]func(){}

var keyActions = map[uint16]func(){}

func unknownAction() {
	panic("unrecognized keystroke")
}

func act(a func()) {
	defer func() {
		if er := recover(); er != nil {
			e, ok := er.(string)
			if ok {
				message = e
			} else {
				message = "unknown error"
			}
		}
	}()
	a()
}

func updateSize() {
	width = t.Width()
	height = t.Height()
}

func display() {
	t.Clear()

	messageWriter := t.Writer(0, height-1, t.WHITE, t.BLACK)
	fmt.Fprint(messageWriter, message)

	t.Present()
}

func UIMain() {
	t.Init()
	defer func() {
		t.Shutdown()
	}()
	updateSize()
	e := t.Event{}
	for {
		display()
		e.Poll()
		switch e.Type {
		case t.EVENT_KEY:
			if e.Ch == 'q' {
				goto Exit
			}
			a, ok := chActions[e.Ch]
			if !ok {
				a, ok = keyActions[e.Key]
			}
			if ok {
				act(a)
			} else {
				act(unknownAction)
			}
		case t.EVENT_RESIZE:
			updateSize()
		default:
			log.Print("warning: unknown event type")
		}
	}

Exit:
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/jyggen/pingu/pingu"
	"github.com/nlopes/slack"
	"github.com/spf13/viper"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"
)

type plugin struct {
	channel      string
	client       *client
	global       *leaderboard
	leaderboards leaderboardList
}

var leaderboardRegex *regexp.Regexp
var version string

func init() {
	leaderboardRegex = regexp.MustCompile("^!leaderboard(?: ([\\d]{4}))?$")
}

func New(c *viper.Viper) pingu.Plugin {
	return pingu.Plugin(&plugin{
		channel: c.GetString("aoc.channel"),
		client: &client{
			httpClient: &http.Client{
				Timeout: c.GetDuration("aoc.timeout") * time.Second,
			},
			ownerId: c.GetInt("aoc.owner"),
			session: c.GetString("aoc.session"),
		},
		global: &leaderboard{},
	})
}

func (pl *plugin) Author() pingu.Author {
	return pingu.Author{
		Email: "jonas@stendahl.me",
		Name:  "Jonas Stendahl",
	}
}

func (pl *plugin) Commands() pingu.Commands {
	return pingu.Commands{
		&pingu.Command{
			Description: "Prints either the global leaderboard, or the leaderboard for a specific year.",
			Func:        pl.postLeaderboard,
			Trigger:     leaderboardRegex,
		},
		&pingu.Command{
			Description: "Forces a refresh of all leaderboards.",
			Func: func(pi *pingu.Pingu, ev *slack.MessageEvent) {
				if ev.Channel != pl.channel {
					pi.Reply(ev, fmt.Sprintf("Noot! Noot! That command is only available in <#%s>!", pl.channel))
					return
				}

				pl.refreshLeaderboards(pi)
			},
			Trigger: regexp.MustCompile("^!refresh$"),
		},
	}
}

func (pl *plugin) Name() string {
	return "Advent of Code"
}

func (pl *plugin) Tasks() pingu.Tasks {
	return pingu.Tasks{
		&pingu.Task{
			Func:     pl.refreshLeaderboards,
			Interval: time.Hour,
		},
	}
}

func (pl *plugin) Version() string {
	return version
}

func (pl *plugin) announceChanges(pi *pingu.Pingu, a leaderboard, b leaderboard) {
	availableStars := calculateAvailableStars(time.Now(), b.Year)

OurLoop:
	for _, our := range b.Members {
		for _, their := range a.Members {
			if our.Id == their.Id {
				if our.TotalStars != their.TotalStars {
					diff := calculateDifference(our.Stars, their.Stars)
					diffLen := len(diff)

					if diffLen != 0 {
						starsMessage := pl.buildChangeMessage(diff)

						var starsLabel string

						if diffLen != 1 {
							starsLabel = "stars"
						} else {
							starsLabel = "star"
						}

						message := fmt.Sprintf("_%s_ earned *%d %s* by completing _%s_", our.Name, diffLen, starsLabel, starsMessage)

						if their.Position > our.Position {
							message += fmt.Sprintf(", moving up to *position %d*", our.Position)
						} else if their.Position < our.Position {
							message += fmt.Sprintf(", moving down to *position %d*", our.Position)
						} else {
							message += fmt.Sprintf(", staying at *position %d*", our.Position)
						}

						if our.TotalStars != 1 {
							starsLabel = "stars"
						} else {
							starsLabel = "star"
						}

						pi.Say(message+fmt.Sprintf(
							" with *%d %s* (%.2f%%)!",
							our.TotalStars,
							starsLabel,
							float64(our.TotalStars)/float64(availableStars)*100,
						), pl.channel)
					}
				}

				continue OurLoop
			}
		}

		var starsLabel string

		if our.TotalStars != 1 {
			starsLabel = "stars"
		} else {
			starsLabel = "star"
		}

		pi.Say(fmt.Sprintf(
			"Noot! Noot! _%s_ has joined our ranks, starting at position *%d* with *%d %s* (%.2f%%) collected.",
			our.Name,
			our.Position,
			our.TotalStars,
			starsLabel,
			float64(our.TotalStars)/float64(availableStars)*100,
		), pl.channel)
	}

TheirLoop:
	for _, their := range a.Members {
		for _, our := range b.Members {
			if their.Id == our.Id {
				continue TheirLoop
			}
		}

		pi.Say(fmt.Sprintf(
			"Noot! Noot! It seems like _%s_ has left our ranks. The leaderboards have been recalculated.",
			their.Name,
		), pl.channel)
	}
}

func (pl *plugin) buildChangeMessage(l starList) string {
	numOfStars := len(l)

	if numOfStars == 0 {
		return ""
	}

	stars := make([]string, numOfStars)

	for i, s := range l {
		stars[i] = fmt.Sprintf("Day %d Part %d (%d)", s.Day, s.Part, s.Year)
	}

	starsString := strings.Join([]string{
		strings.Join(stars[:numOfStars-1], "_, _"),
		stars[numOfStars-1],
	}, "_ and _")

	if starsString[0:7] == "_ and _" {
		starsString = starsString[7:]
	}

	return starsString
}

func (pl *plugin) buildLeaderboard(l *leaderboard) string {
	availableStars := calculateAvailableStars(time.Now(), l.Year)
	message := ""

	for _, m := range l.Members {
		var starsLabel string

		if m.TotalStars != 1 {
			starsLabel = "stars"
		} else {
			starsLabel = "star"
		}

		message += fmt.Sprintf(
			"*%d.* _%s_ with *%d %s* (%.2f%%) collected.\n",
			m.Position,
			m.Name,
			m.TotalStars,
			starsLabel,
			float64(m.TotalStars)/float64(availableStars)*100,
		)
	}

	return message
}

func (pl *plugin) postLeaderboard(pi *pingu.Pingu, ev *slack.MessageEvent) {
	if ev.Channel != pl.channel {
		pi.Reply(ev, fmt.Sprintf("Noot! Noot! That command is only available in <#%s>!", pl.channel))
		return
	}

	var board *leaderboard

	match := leaderboardRegex.FindStringSubmatch(ev.Text)

	if len(match) == 2 {
		year, _ := strconv.Atoi(match[1])

		for _, l := range pl.leaderboards {
			if l.Year == year {
				board = l
			}
		}

		if board == nil {
			pi.Reply(ev, fmt.Sprintf("Noot! Noot! %d does not have a leaderboard!", year))
		}
	} else {
		board = pl.global
	}

	pi.Say(pl.buildLeaderboard(board), ev.Channel)
}

func (pl *plugin) refreshGlobalLeaderboard() {
	members := make(map[int]*member)

	for _, l := range pl.leaderboards {
		for _, m := range l.Members {
			if _, ok := members[m.Id]; !ok {
				members[m.Id] = &member{
					Id:   m.Id,
					Name: m.Name,
				}
			}

			members[m.Id].GlobalScore += m.GlobalScore
			members[m.Id].LocalScore += m.LocalScore
			members[m.Id].Stars = append(members[m.Id].Stars, m.Stars...)
			members[m.Id].TotalStars += m.TotalStars

			if members[m.Id].LastStarAt.Before(m.LastStarAt) {
				members[m.Id].LastStarAt = m.LastStarAt
			}
		}
	}

	pl.global.Members = make(memberList, len(members))
	i := 0

	for _, m := range members {
		pl.global.Members[i] = m
		i++
	}

	pl.global.Sort()
}

func (pl *plugin) refreshLeaderboards(pi *pingu.Pingu) {
Loop:
	for _, year := range getValidYears(time.Now()) {
		for _, l := range pl.leaderboards {
			if l.Year == year {
				continue Loop
			}
		}

		pl.leaderboards = append(pl.leaderboards, &leaderboard{
			Year: year,
		})
	}

	var wg sync.WaitGroup

	wg.Add(len(pl.leaderboards))

	for _, l := range pl.leaderboards {
		go func(l *leaderboard) {
			before := *l
			err := l.Refresh(pl.client)
			after := *l

			if err != nil {
				pi.Logger().Error(err)
			} else if len(before.Members) != 0 {
				pl.announceChanges(pi, before, after)
			}

			wg.Done()
		}(l)
	}

	wg.Wait()
	pl.refreshGlobalLeaderboard()
}
<commit_msg>Refresh leaderboard every 5 minutes<commit_after>package main

import (
	"fmt"
	"github.com/jyggen/pingu/pingu"
	"github.com/nlopes/slack"
	"github.com/spf13/viper"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"
)

type plugin struct {
	channel      string
	client       *client
	global       *leaderboard
	leaderboards leaderboardList
}

var leaderboardRegex *regexp.Regexp
var version string

func init() {
	leaderboardRegex = regexp.MustCompile("^!leaderboard(?: ([\\d]{4}))?$")
}

func New(c *viper.Viper) pingu.Plugin {
	return pingu.Plugin(&plugin{
		channel: c.GetString("aoc.channel"),
		client: &client{
			httpClient: &http.Client{
				Timeout: c.GetDuration("aoc.timeout") * time.Second,
			},
			ownerId: c.GetInt("aoc.owner"),
			session: c.GetString("aoc.session"),
		},
		global: &leaderboard{},
	})
}

func (pl *plugin) Author() pingu.Author {
	return pingu.Author{
		Email: "jonas@stendahl.me",
		Name:  "Jonas Stendahl",
	}
}

func (pl *plugin) Commands() pingu.Commands {
	return pingu.Commands{
		&pingu.Command{
			Description: "Prints either the global leaderboard, or the leaderboard for a specific year.",
			Func:        pl.postLeaderboard,
			Trigger:     leaderboardRegex,
		},
		&pingu.Command{
			Description: "Forces a refresh of all leaderboards.",
			Func: func(pi *pingu.Pingu, ev *slack.MessageEvent) {
				if ev.Channel != pl.channel {
					pi.Reply(ev, fmt.Sprintf("Noot! Noot! That command is only available in <#%s>!", pl.channel))
					return
				}

				pl.refreshLeaderboards(pi)
			},
			Trigger: regexp.MustCompile("^!refresh$"),
		},
	}
}

func (pl *plugin) Name() string {
	return "Advent of Code"
}

func (pl *plugin) Tasks() pingu.Tasks {
	return pingu.Tasks{
		&pingu.Task{
			Func:     pl.refreshLeaderboards,
			Interval: time.Minute * 5,
		},
	}
}

func (pl *plugin) Version() string {
	return version
}

func (pl *plugin) announceChanges(pi *pingu.Pingu, a leaderboard, b leaderboard) {
	availableStars := calculateAvailableStars(time.Now(), b.Year)

OurLoop:
	for _, our := range b.Members {
		for _, their := range a.Members {
			if our.Id == their.Id {
				if our.TotalStars != their.TotalStars {
					diff := calculateDifference(our.Stars, their.Stars)
					diffLen := len(diff)

					if diffLen != 0 {
						starsMessage := pl.buildChangeMessage(diff)

						var starsLabel string

						if diffLen != 1 {
							starsLabel = "stars"
						} else {
							starsLabel = "star"
						}

						message := fmt.Sprintf("_%s_ earned *%d %s* by completing _%s_", our.Name, diffLen, starsLabel, starsMessage)

						if their.Position > our.Position {
							message += fmt.Sprintf(", moving up to *position %d*", our.Position)
						} else if their.Position < our.Position {
							message += fmt.Sprintf(", moving down to *position %d*", our.Position)
						} else {
							message += fmt.Sprintf(", staying at *position %d*", our.Position)
						}

						if our.TotalStars != 1 {
							starsLabel = "stars"
						} else {
							starsLabel = "star"
						}

						pi.Say(message+fmt.Sprintf(
							" with *%d %s* (%.2f%%)!",
							our.TotalStars,
							starsLabel,
							float64(our.TotalStars)/float64(availableStars)*100,
						), pl.channel)
					}
				}

				continue OurLoop
			}
		}

		var starsLabel string

		if our.TotalStars != 1 {
			starsLabel = "stars"
		} else {
			starsLabel = "star"
		}

		pi.Say(fmt.Sprintf(
			"Noot! Noot! _%s_ has joined our ranks, starting at position *%d* with *%d %s* (%.2f%%) collected.",
			our.Name,
			our.Position,
			our.TotalStars,
			starsLabel,
			float64(our.TotalStars)/float64(availableStars)*100,
		), pl.channel)
	}

TheirLoop:
	for _, their := range a.Members {
		for _, our := range b.Members {
			if their.Id == our.Id {
				continue TheirLoop
			}
		}

		pi.Say(fmt.Sprintf(
			"Noot! Noot! It seems like _%s_ has left our ranks. The leaderboards have been recalculated.",
			their.Name,
		), pl.channel)
	}
}

func (pl *plugin) buildChangeMessage(l starList) string {
	numOfStars := len(l)

	if numOfStars == 0 {
		return ""
	}

	stars := make([]string, numOfStars)

	for i, s := range l {
		stars[i] = fmt.Sprintf("Day %d Part %d (%d)", s.Day, s.Part, s.Year)
	}

	starsString := strings.Join([]string{
		strings.Join(stars[:numOfStars-1], "_, _"),
		stars[numOfStars-1],
	}, "_ and _")

	if starsString[0:7] == "_ and _" {
		starsString = starsString[7:]
	}

	return starsString
}

func (pl *plugin) buildLeaderboard(l *leaderboard) string {
	availableStars := calculateAvailableStars(time.Now(), l.Year)
	message := ""

	for _, m := range l.Members {
		var starsLabel string

		if m.TotalStars != 1 {
			starsLabel = "stars"
		} else {
			starsLabel = "star"
		}

		message += fmt.Sprintf(
			"*%d.* _%s_ with *%d %s* (%.2f%%) collected.\n",
			m.Position,
			m.Name,
			m.TotalStars,
			starsLabel,
			float64(m.TotalStars)/float64(availableStars)*100,
		)
	}

	return message
}

func (pl *plugin) postLeaderboard(pi *pingu.Pingu, ev *slack.MessageEvent) {
	if ev.Channel != pl.channel {
		pi.Reply(ev, fmt.Sprintf("Noot! Noot! That command is only available in <#%s>!", pl.channel))
		return
	}

	var board *leaderboard

	match := leaderboardRegex.FindStringSubmatch(ev.Text)

	if len(match) == 2 {
		year, _ := strconv.Atoi(match[1])

		for _, l := range pl.leaderboards {
			if l.Year == year {
				board = l
			}
		}

		if board == nil {
			pi.Reply(ev, fmt.Sprintf("Noot! Noot! %d does not have a leaderboard!", year))
		}
	} else {
		board = pl.global
	}

	pi.Say(pl.buildLeaderboard(board), ev.Channel)
}

func (pl *plugin) refreshGlobalLeaderboard() {
	members := make(map[int]*member)

	for _, l := range pl.leaderboards {
		for _, m := range l.Members {
			if _, ok := members[m.Id]; !ok {
				members[m.Id] = &member{
					Id:   m.Id,
					Name: m.Name,
				}
			}

			members[m.Id].GlobalScore += m.GlobalScore
			members[m.Id].LocalScore += m.LocalScore
			members[m.Id].Stars = append(members[m.Id].Stars, m.Stars...)
			members[m.Id].TotalStars += m.TotalStars

			if members[m.Id].LastStarAt.Before(m.LastStarAt) {
				members[m.Id].LastStarAt = m.LastStarAt
			}
		}
	}

	pl.global.Members = make(memberList, len(members))
	i := 0

	for _, m := range members {
		pl.global.Members[i] = m
		i++
	}

	pl.global.Sort()
}

func (pl *plugin) refreshLeaderboards(pi *pingu.Pingu) {
Loop:
	for _, year := range getValidYears(time.Now()) {
		for _, l := range pl.leaderboards {
			if l.Year == year {
				continue Loop
			}
		}

		pl.leaderboards = append(pl.leaderboards, &leaderboard{
			Year: year,
		})
	}

	var wg sync.WaitGroup

	wg.Add(len(pl.leaderboards))

	for _, l := range pl.leaderboards {
		go func(l *leaderboard) {
			before := *l
			err := l.Refresh(pl.client)
			after := *l

			if err != nil {
				pi.Logger().Error(err)
			} else if len(before.Members) != 0 {
				pl.announceChanges(pi, before, after)
			}

			wg.Done()
		}(l)
	}

	wg.Wait()
	pl.refreshGlobalLeaderboard()
}
<|endoftext|>


<commit_before>package policy_repo

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"regexp"
	"strconv"
	"strings"

	"github.com/noironetworks/cilium-net/common"
	cnc "github.com/noironetworks/cilium-net/common/client"
	"github.com/noironetworks/cilium-net/common/types"

	"github.com/codegangsta/cli"
	l "github.com/op/go-logging"
)

var (
	client             *cnc.Client
	ignoredMasksSource = []string{".git"}
	ignoredMasks       []*regexp.Regexp
	log                = l.MustGetLogger("cilium-net-policy-repo")
	CliCommand         cli.Command
)

func init() {
	ignoredMasks = make([]*regexp.Regexp, len(ignoredMasksSource))

	for i, _ := range ignoredMasksSource {
		ignoredMasks[i] = regexp.MustCompile(ignoredMasksSource[i])
	}

	CliCommand = cli.Command{
		Name:  "policy",
		Usage: "Manage policy operations",
		Subcommands: []cli.Command{
			{
				Name:      "validate",
				Usage:     "Validate a policy (sub)tree",
				Action:    validatePolicy,
				ArgsUsage: "<path>",
				Flags: []cli.Flag{
					cli.BoolFlag{
						Name:  "dump, d",
						Usage: "Dump parsed policy tree after validation",
					},
				},
				Before: verifyArgumentsValidate,
			},
			{
				Name:      "import",
				Usage:     "Import a policy (sub)tree",
				Action:    importPolicy,
				ArgsUsage: "<path>",
				Before:    verifyArgumentsValidate,
			},
			{
				Name:      "dump",
				Usage:     "Dump policy (sub)tree",
				Action:    dumpPolicy,
				ArgsUsage: "<path>",
				Before:    initEnv,
			},
			{
				Name:      "delete",
				Usage:     "Delete policy (sub)tree",
				Action:    deletePolicy,
				ArgsUsage: "<path>",
				Before:    verifyArgumentsValidate,
			},
			{
				Name:   "get-id",
				Usage:  "Lookup security context id",
				Action: getSecID,
				Flags: []cli.Flag{
					cli.BoolFlag{
						Name:  "list, l",
						Usage: "List all reserved IDs",
					},
				},
				Before: initEnv,
			},
			{
				Name: "allowed",
				Usage: "Verifies if source ID or LABEL(s) is allowed to consume destination ID or LABEL(s). " +
					"LABEL is represented as SOURCE:KEY[=VALUE]",
				Action: verifyPolicy,
				Flags: []cli.Flag{
					cli.StringSliceFlag{
						Name: "source, s",
					},
					cli.StringSliceFlag{
						Name: "destination, d",
					},
				},
				Before: verifyArgumentsPolicy,
			},
		},
	}
}

func initEnv(ctx *cli.Context) error {
	if ctx.GlobalBool("debug") {
		common.SetupLOG(log, "DEBUG")
	} else {
		common.SetupLOG(log, "INFO")
	}

	var (
		c   *cnc.Client
		err error
	)
	if host := ctx.GlobalString("host"); host == "" {
		c, err = cnc.NewDefaultClient()
	} else {
		c, err = cnc.NewClient(host, nil, nil, nil)
	}
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error while creating cilium-client: %s\n", err)
		return fmt.Errorf("Error while creating cilium-client: %s", err)
	}
	client = c

	return nil
}

func verifyArgumentsValidate(ctx *cli.Context) error {
	path := ctx.Args().First()
	if path == "" {
		return fmt.Errorf("Error: empty path")
	}
	return initEnv(ctx)
}

func verifyAllowedSlice(slice []string) error {
	for i, v := range slice {
		if _, err := strconv.ParseUint(v, 10, 32); err != nil {
			// can fail which means it needs to be a label
			types.ParseLabel(v)
		} else if i != 0 {
			return fmt.Errorf("value %q: must be only one unsigned "+
				"number or label(s) in format of SOURCE:KEY[=VALUE]", v)
		}
	}
	return nil
}

func verifyArgumentsPolicy(ctx *cli.Context) error {
	srcSlice := ctx.StringSlice("source")
	if len(srcSlice) == 0 {
		return fmt.Errorf("Empty source")
	}

	dstSlice := ctx.StringSlice("destination")
	if len(srcSlice) == 0 {
		return fmt.Errorf("Empty destination")
	}

	if err := verifyAllowedSlice(srcSlice); err != nil {
		return fmt.Errorf("Invalid source: %s", err)
	}

	if err := verifyAllowedSlice(dstSlice); err != nil {
		return fmt.Errorf("Invalid destination: %s", err)
	}

	return initEnv(ctx)
}

func getContext(content []byte, offset int64) (int, string, int) {
	if offset >= int64(len(content)) || offset < 0 {
		return 0, fmt.Sprintf("[error: Offset %d is out of bounds 0..%d]", offset, len(content)), 0
	}

	lineN := strings.Count(string(content[:offset]), "\n") + 1

	start := strings.LastIndexByte(string(content[:offset]), '\n')
	if start == -1 {
		start = 0
	} else {
		start++
	}

	end := strings.IndexByte(string(content[start:]), '\n')
	l := ""
	if end == -1 {
		l = string(content[start:])
	} else {
		end = end + start
		l = string(content[start:end])
	}

	return lineN, l, (int(offset) - start)
}

func handleUnmarshalError(f string, content []byte, err error) error {
	switch e := err.(type) {
	case *json.SyntaxError:
		line, ctx, off := getContext(content, e.Offset)

		preoff := off - 1
		pre := make([]byte, preoff)
		copy(pre, ctx[:preoff])
		for i := 0; i < preoff && i < len(pre); i++ {
			if pre[i] != '\t' {
				pre[i] = ' '
			}
		}

		return fmt.Errorf("Error: %s:%d: Syntax error at offset %d:\n%s\n%s^",
			path.Base(f), line, off, ctx, pre)
	case *json.UnmarshalTypeError:
		line, ctx, off := getContext(content, e.Offset)
		return fmt.Errorf("Error: %s:%d: Unable to assign value '%s' to type '%v':\n%s\n%*c",
			path.Base(f), line, e.Value, e.Type, ctx, off, '^')
	default:
		return fmt.Errorf("Error: %s: Unknown error:%s", path.Base(f), err)
	}
}

func loadPolicyFile(path string) (*types.PolicyNode, error) {
	log.Debugf("Loading file %s", path)

	content, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, err
	}

	var policyNode types.PolicyNode
	err = json.Unmarshal(content, &policyNode)
	if err != nil {
		return nil, handleUnmarshalError(path, content, err)
	}

	return &policyNode, nil
}

func ignoredFile(name string) bool {
	for i := range ignoredMasks {
		if ignoredMasks[i].MatchString(name) {
			log.Debugf("Ignoring file %s", name)
			return true
		}
	}

	return false
}

func loadPolicy(name string) (*types.PolicyNode, error) {
	log.Debugf("Entering directory %s...", name)

	if fi, err := os.Stat(name); err != nil {
		return nil, err
	} else if fi.Mode().IsRegular() {
		return loadPolicyFile(name)
	} else if !fi.Mode().IsDir() {
		return nil, fmt.Errorf("Error: %s is not a file or a directory", name)
	}

	files, err := ioutil.ReadDir(name)
	if err != nil {
		return nil, err
	}

	var node *types.PolicyNode

	// process all files first
	for _, f := range files {
		if f.IsDir() || ignoredFile(path.Base(f.Name())) {
			continue
		}

		if p, err := loadPolicyFile(name + "/" + f.Name()); err != nil {
			return nil, err
		} else {
			if node != nil {
				if err := node.Merge(p); err != nil {
					return nil, fmt.Errorf("Error: %s: %s", f.Name(), err)
				}
			} else {
				node = p
			}
		}
	}

	// recursive search
	for _, f := range files {
		if f.IsDir() {
			if ignoredFile(path.Base(f.Name())) {
				continue
			}
			subpath := name + "/" + f.Name()
			if p, err := loadPolicy(subpath); err != nil {
				return nil, err
			} else {
				if p.Name == "" {
					return nil, fmt.Errorf("Policy node import from %s did not derive a name",
						subpath)
				}

				node.AddChild(p.Name, p)
			}
		}
	}

	log.Debugf("Leaving directory %s...", name)

	return node, nil
}

func importPolicy(ctx *cli.Context) {
	path := ctx.Args().First()
	if node, err := loadPolicy(path); err != nil {
		fmt.Fprintf(os.Stderr, "Could not import policy %s: %s\n", path, err)
		os.Exit(1)
	} else {
		log.Debugf("Constructed policy object for import %+v", node)

		// Ignore request if no policies have been found
		if node == nil {
			return
		}

		if err := client.PolicyAdd(node.Name, node); err != nil {
			fmt.Fprintf(os.Stderr, "Could not import policy directory %s: %s\n", path, err)
			os.Exit(1)
		}
	}
}

func prettyPrint(node *types.PolicyNode) {
	if node == nil {
		fmt.Println("No policy loaded.")
	} else if b, err := json.MarshalIndent(node, "", "  "); err != nil {
		fmt.Fprintf(os.Stderr, "Could not marshal response: %s\n", err)
	} else {
		fmt.Printf("%s\n", b)
	}
}

func validatePolicy(ctx *cli.Context) {
	path := ctx.Args().First()
	if node, err := loadPolicy(path); err != nil {
		fmt.Fprintf(os.Stderr, "Validation of %s failed\n%s\n", path, err)
		os.Exit(1)
	} else {
		fmt.Printf("All policy elements are valid.\n")

		if ctx.Bool("dump") {
			fmt.Printf("%s\n", node.DebugString(1))
			prettyPrint(node)
		}
	}
}

func dumpPolicy(ctx *cli.Context) {
	path := ctx.Args().First()

	if path == "" {
		path = common.GlobalLabelPrefix
	}

	n, err := client.PolicyGet(path)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Could not retrieve policy for: %s: %s\n", path, err)
		os.Exit(1)
	}

	prettyPrint(n)
}

func deletePolicy(ctx *cli.Context) {
	path := ctx.Args().First()

	if err := client.PolicyDelete(path); err != nil {
		fmt.Fprintf(os.Stderr, "Could not retrieve policy for: %s: %s\n", path, err)
		os.Exit(1)
	}
}

func getSecID(ctx *cli.Context) {
	if ctx.Bool("list") {
		for k, v := range types.ResDec {
			fmt.Printf("%-15s %3d\n", k, v)
		}
		return
	}

	lbl := ctx.Args().First()

	if id := types.GetID(lbl); id != types.ID_UNKNOWN {
		fmt.Printf("%d\n", id)
	} else {
		os.Exit(1)
	}
}

func parseAllowedSlice(slice []string) ([]types.Label, error) {
	inLabels := []types.Label{}
	id := uint32(0)

	for _, v := range slice {
		if n, err := strconv.ParseUint(v, 10, 32); err != nil {
			// can fail which means it needs to be a label
			lbl := types.ParseLabel(v)
			inLabels = append(inLabels, *lbl)
		} else {
			if id != 0 {
				return nil, fmt.Errorf("More than one security ID provided")
			}

			id = uint32(n)
		}
	}

	if id != 0 {
		if len(inLabels) > 0 {
			return nil, fmt.Errorf("You can only specify either ID or labels")
		}

		ctx, err := client.GetLabels(id)
		if err != nil {
			return nil, fmt.Errorf("Unable to retrieve labels for ID %d: %s", id, err)
		}
		if ctx == nil {
			return nil, fmt.Errorf("ID %d not found", id)
		}

		return ctx.Labels.ToSlice(), nil
	} else {
		if len(inLabels) == 0 {
			return nil, fmt.Errorf("No label or security ID provided")
		}

		return inLabels, nil
	}
}

func verifyPolicy(ctx *cli.Context) {
	srcInSlice := ctx.StringSlice("source")
	dstInSlice := ctx.StringSlice("destination")

	srcSlice, err := parseAllowedSlice(srcInSlice)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Invalid source: %s\n", err)
		os.Exit(1)
	}

	dstSlice, err := parseAllowedSlice(dstInSlice)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Invalid destination: %s\n", err)
		os.Exit(1)
	}

	searchCtx := types.SearchContext{
		Trace: types.TRACE_ENABLED,
		From:  srcSlice,
		To:    dstSlice,
	}

	scr, err := client.PolicyCanConsume(&searchCtx)

	if err != nil {
		fmt.Fprintf(os.Stderr, "Error while retrieving policy consume result: %s\n", err)
		os.Exit(1)
	}
	bytes.NewBuffer(scr.Logging).WriteTo(os.Stdout)
}
<commit_msg>cilium: Allow reading policy from stdin<commit_after>package policy_repo

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"regexp"
	"strconv"
	"strings"

	"github.com/noironetworks/cilium-net/common"
	cnc "github.com/noironetworks/cilium-net/common/client"
	"github.com/noironetworks/cilium-net/common/types"

	"github.com/codegangsta/cli"
	l "github.com/op/go-logging"
)

var (
	client             *cnc.Client
	ignoredMasksSource = []string{".git"}
	ignoredMasks       []*regexp.Regexp
	log                = l.MustGetLogger("cilium-net-policy-repo")
	CliCommand         cli.Command
)

func init() {
	ignoredMasks = make([]*regexp.Regexp, len(ignoredMasksSource))

	for i, _ := range ignoredMasksSource {
		ignoredMasks[i] = regexp.MustCompile(ignoredMasksSource[i])
	}

	CliCommand = cli.Command{
		Name:  "policy",
		Usage: "Manage policy operations",
		Subcommands: []cli.Command{
			{
				Name:      "validate",
				Usage:     "Validate a policy (sub)tree",
				Action:    validatePolicy,
				ArgsUsage: "<path>",
				Flags: []cli.Flag{
					cli.BoolFlag{
						Name:  "dump, d",
						Usage: "Dump parsed policy tree after validation",
					},
				},
				Before: verifyArgumentsValidate,
			},
			{
				Name:      "import",
				Usage:     "Import a policy (sub)tree",
				Action:    importPolicy,
				ArgsUsage: "<path>",
				Before:    verifyArgumentsValidate,
			},
			{
				Name:      "dump",
				Usage:     "Dump policy (sub)tree",
				Action:    dumpPolicy,
				ArgsUsage: "<path>",
				Before:    initEnv,
			},
			{
				Name:      "delete",
				Usage:     "Delete policy (sub)tree",
				Action:    deletePolicy,
				ArgsUsage: "<path>",
				Before:    verifyArgumentsValidate,
			},
			{
				Name:   "get-id",
				Usage:  "Lookup security context id",
				Action: getSecID,
				Flags: []cli.Flag{
					cli.BoolFlag{
						Name:  "list, l",
						Usage: "List all reserved IDs",
					},
				},
				Before: initEnv,
			},
			{
				Name: "allowed",
				Usage: "Verifies if source ID or LABEL(s) is allowed to consume destination ID or LABEL(s). " +
					"LABEL is represented as SOURCE:KEY[=VALUE]",
				Action: verifyPolicy,
				Flags: []cli.Flag{
					cli.StringSliceFlag{
						Name: "source, s",
					},
					cli.StringSliceFlag{
						Name: "destination, d",
					},
				},
				Before: verifyArgumentsPolicy,
			},
		},
	}
}

func initEnv(ctx *cli.Context) error {
	if ctx.GlobalBool("debug") {
		common.SetupLOG(log, "DEBUG")
	} else {
		common.SetupLOG(log, "INFO")
	}

	var (
		c   *cnc.Client
		err error
	)
	if host := ctx.GlobalString("host"); host == "" {
		c, err = cnc.NewDefaultClient()
	} else {
		c, err = cnc.NewClient(host, nil, nil, nil)
	}
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error while creating cilium-client: %s\n", err)
		return fmt.Errorf("Error while creating cilium-client: %s", err)
	}
	client = c

	return nil
}

func verifyArgumentsValidate(ctx *cli.Context) error {
	path := ctx.Args().First()
	if path == "" {
		return fmt.Errorf("Error: empty path")
	}
	return initEnv(ctx)
}

func verifyAllowedSlice(slice []string) error {
	for i, v := range slice {
		if _, err := strconv.ParseUint(v, 10, 32); err != nil {
			// can fail which means it needs to be a label
			types.ParseLabel(v)
		} else if i != 0 {
			return fmt.Errorf("value %q: must be only one unsigned "+
				"number or label(s) in format of SOURCE:KEY[=VALUE]", v)
		}
	}
	return nil
}

func verifyArgumentsPolicy(ctx *cli.Context) error {
	srcSlice := ctx.StringSlice("source")
	if len(srcSlice) == 0 {
		return fmt.Errorf("Empty source")
	}

	dstSlice := ctx.StringSlice("destination")
	if len(srcSlice) == 0 {
		return fmt.Errorf("Empty destination")
	}

	if err := verifyAllowedSlice(srcSlice); err != nil {
		return fmt.Errorf("Invalid source: %s", err)
	}

	if err := verifyAllowedSlice(dstSlice); err != nil {
		return fmt.Errorf("Invalid destination: %s", err)
	}

	return initEnv(ctx)
}

func getContext(content []byte, offset int64) (int, string, int) {
	if offset >= int64(len(content)) || offset < 0 {
		return 0, fmt.Sprintf("[error: Offset %d is out of bounds 0..%d]", offset, len(content)), 0
	}

	lineN := strings.Count(string(content[:offset]), "\n") + 1

	start := strings.LastIndexByte(string(content[:offset]), '\n')
	if start == -1 {
		start = 0
	} else {
		start++
	}

	end := strings.IndexByte(string(content[start:]), '\n')
	l := ""
	if end == -1 {
		l = string(content[start:])
	} else {
		end = end + start
		l = string(content[start:end])
	}

	return lineN, l, (int(offset) - start)
}

func handleUnmarshalError(f string, content []byte, err error) error {
	switch e := err.(type) {
	case *json.SyntaxError:
		line, ctx, off := getContext(content, e.Offset)

		if off <= 1 {
			return fmt.Errorf("Error: No policy")
		}

		preoff := off - 1
		pre := make([]byte, preoff)
		copy(pre, ctx[:preoff])
		for i := 0; i < preoff && i < len(pre); i++ {
			if pre[i] != '\t' {
				pre[i] = ' '
			}
		}

		return fmt.Errorf("Error: %s:%d: Syntax error at offset %d:\n%s\n%s^",
			path.Base(f), line, off, ctx, pre)
	case *json.UnmarshalTypeError:
		line, ctx, off := getContext(content, e.Offset)
		return fmt.Errorf("Error: %s:%d: Unable to assign value '%s' to type '%v':\n%s\n%*c",
			path.Base(f), line, e.Value, e.Type, ctx, off, '^')
	default:
		return fmt.Errorf("Error: %s: Unknown error:%s", path.Base(f), err)
	}
}

func loadPolicyFile(path string) (*types.PolicyNode, error) {
	var content []byte
	var err error
	log.Debugf("Loading file %s", path)

	if path == "-" {
		content, err = ioutil.ReadAll(bufio.NewReader(os.Stdin))
	} else {
		content, err = ioutil.ReadFile(path)
	}

	if err != nil {
		return nil, err
	}

	var policyNode types.PolicyNode
	err = json.Unmarshal(content, &policyNode)
	if err != nil {
		return nil, handleUnmarshalError(path, content, err)
	}

	return &policyNode, nil
}

func ignoredFile(name string) bool {
	for i := range ignoredMasks {
		if ignoredMasks[i].MatchString(name) {
			log.Debugf("Ignoring file %s", name)
			return true
		}
	}

	return false
}

func loadPolicy(name string) (*types.PolicyNode, error) {
	log.Debugf("Entering directory %s...", name)

	if name == "-" {
		return loadPolicyFile(name)
	}

	if fi, err := os.Stat(name); err != nil {
		return nil, err
	} else if fi.Mode().IsRegular() {
		return loadPolicyFile(name)
	} else if !fi.Mode().IsDir() {
		return nil, fmt.Errorf("Error: %s is not a file or a directory", name)
	}

	files, err := ioutil.ReadDir(name)
	if err != nil {
		return nil, err
	}

	var node *types.PolicyNode

	// process all files first
	for _, f := range files {
		if f.IsDir() || ignoredFile(path.Base(f.Name())) {
			continue
		}

		if p, err := loadPolicyFile(name + "/" + f.Name()); err != nil {
			return nil, err
		} else {
			if node != nil {
				if err := node.Merge(p); err != nil {
					return nil, fmt.Errorf("Error: %s: %s", f.Name(), err)
				}
			} else {
				node = p
			}
		}
	}

	// recursive search
	for _, f := range files {
		if f.IsDir() {
			if ignoredFile(path.Base(f.Name())) {
				continue
			}
			subpath := name + "/" + f.Name()
			if p, err := loadPolicy(subpath); err != nil {
				return nil, err
			} else {
				if p.Name == "" {
					return nil, fmt.Errorf("Policy node import from %s did not derive a name",
						subpath)
				}

				node.AddChild(p.Name, p)
			}
		}
	}

	log.Debugf("Leaving directory %s...", name)

	return node, nil
}

func importPolicy(ctx *cli.Context) {
	path := ctx.Args().First()
	if node, err := loadPolicy(path); err != nil {
		fmt.Fprintf(os.Stderr, "Could not import policy %s: %s\n", path, err)
		os.Exit(1)
	} else {
		log.Debugf("Constructed policy object for import %+v", node)

		// Ignore request if no policies have been found
		if node == nil {
			return
		}

		if err := client.PolicyAdd(node.Name, node); err != nil {
			fmt.Fprintf(os.Stderr, "Could not import policy directory %s: %s\n", path, err)
			os.Exit(1)
		}
	}
}

func prettyPrint(node *types.PolicyNode) {
	if node == nil {
		fmt.Println("No policy loaded.")
	} else if b, err := json.MarshalIndent(node, "", "  "); err != nil {
		fmt.Fprintf(os.Stderr, "Could not marshal response: %s\n", err)
	} else {
		fmt.Printf("%s\n", b)
	}
}

func validatePolicy(ctx *cli.Context) {
	path := ctx.Args().First()
	if node, err := loadPolicy(path); err != nil {
		fmt.Fprintf(os.Stderr, "Validation of %s failed\n%s\n", path, err)
		os.Exit(1)
	} else {
		fmt.Printf("All policy elements are valid.\n")

		if ctx.Bool("dump") {
			fmt.Printf("%s\n", node.DebugString(1))
			prettyPrint(node)
		}
	}
}

func dumpPolicy(ctx *cli.Context) {
	path := ctx.Args().First()

	if path == "" {
		path = common.GlobalLabelPrefix
	}

	n, err := client.PolicyGet(path)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Could not retrieve policy for: %s: %s\n", path, err)
		os.Exit(1)
	}

	prettyPrint(n)
}

func deletePolicy(ctx *cli.Context) {
	path := ctx.Args().First()

	if err := client.PolicyDelete(path); err != nil {
		fmt.Fprintf(os.Stderr, "Could not retrieve policy for: %s: %s\n", path, err)
		os.Exit(1)
	}
}

func getSecID(ctx *cli.Context) {
	if ctx.Bool("list") {
		for k, v := range types.ResDec {
			fmt.Printf("%-15s %3d\n", k, v)
		}
		return
	}

	lbl := ctx.Args().First()

	if id := types.GetID(lbl); id != types.ID_UNKNOWN {
		fmt.Printf("%d\n", id)
	} else {
		os.Exit(1)
	}
}

func parseAllowedSlice(slice []string) ([]types.Label, error) {
	inLabels := []types.Label{}
	id := uint32(0)

	for _, v := range slice {
		if n, err := strconv.ParseUint(v, 10, 32); err != nil {
			// can fail which means it needs to be a label
			lbl := types.ParseLabel(v)
			inLabels = append(inLabels, *lbl)
		} else {
			if id != 0 {
				return nil, fmt.Errorf("More than one security ID provided")
			}

			id = uint32(n)
		}
	}

	if id != 0 {
		if len(inLabels) > 0 {
			return nil, fmt.Errorf("You can only specify either ID or labels")
		}

		ctx, err := client.GetLabels(id)
		if err != nil {
			return nil, fmt.Errorf("Unable to retrieve labels for ID %d: %s", id, err)
		}
		if ctx == nil {
			return nil, fmt.Errorf("ID %d not found", id)
		}

		return ctx.Labels.ToSlice(), nil
	} else {
		if len(inLabels) == 0 {
			return nil, fmt.Errorf("No label or security ID provided")
		}

		return inLabels, nil
	}
}

func verifyPolicy(ctx *cli.Context) {
	srcInSlice := ctx.StringSlice("source")
	dstInSlice := ctx.StringSlice("destination")

	srcSlice, err := parseAllowedSlice(srcInSlice)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Invalid source: %s\n", err)
		os.Exit(1)
	}

	dstSlice, err := parseAllowedSlice(dstInSlice)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Invalid destination: %s\n", err)
		os.Exit(1)
	}

	searchCtx := types.SearchContext{
		Trace: types.TRACE_ENABLED,
		From:  srcSlice,
		To:    dstSlice,
	}

	scr, err := client.PolicyCanConsume(&searchCtx)

	if err != nil {
		fmt.Fprintf(os.Stderr, "Error while retrieving policy consume result: %s\n", err)
		os.Exit(1)
	}
	bytes.NewBuffer(scr.Logging).WriteTo(os.Stdout)
}
<|endoftext|>


<commit_before>// Copyright 2016 The Ebiten Authors
//
// 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 twenty48

import (
	"errors"
	"image/color"
	"log"
	"math/rand"
	"sort"
	"strconv"

	"golang.org/x/image/font"
	"golang.org/x/image/font/opentype"

	"github.com/hajimehoshi/ebiten"
	"github.com/hajimehoshi/ebiten/examples/resources/fonts"
	"github.com/hajimehoshi/ebiten/text"
)

var (
	mplusSmallFont  font.Face
	mplusNormalFont font.Face
	mplusBigFont    font.Face
)

func init() {
	tt, err := opentype.Parse(fonts.MPlus1pRegular_ttf)
	if err != nil {
		log.Fatal(err)
	}

	const dpi = 72
	mplusSmallFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
		Size:    24,
		DPI:     dpi,
		Hinting: font.HintingFull,
	})
	if err != nil {
		log.Fatal(err)
	}
	mplusNormalFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
		Size:    32,
		DPI:     dpi,
		Hinting: font.HintingFull,
	})
	if err != nil {
		log.Fatal(err)
	}
	mplusBigFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
		Size:    48,
		DPI:     dpi,
		Hinting: font.HintingFull,
	})
	if err != nil {
		log.Fatal(err)
	}
}

// TileData represents a tile information like a value and a position.
type TileData struct {
	value int
	x     int
	y     int
}

// Tile represents a tile information including TileData and animation states.
type Tile struct {
	current TileData

	// next represents a next tile information after moving.
	// next is empty when the tile is not about to move.
	next TileData

	movingCount       int
	startPoppingCount int
	poppingCount      int
}

// Pos returns the tile's current position.
// Pos is used only at testing so far.
func (t *Tile) Pos() (int, int) {
	return t.current.x, t.current.y
}

// NextPos returns the tile's next position.
// NextPos is used only at testing so far.
func (t *Tile) NextPos() (int, int) {
	return t.next.x, t.next.y
}

// Value returns the tile's current value.
// Value is used only at testing so far.
func (t *Tile) Value() int {
	return t.current.value
}

// NextValue returns the tile's current value.
// NextValue is used only at testing so far.
func (t *Tile) NextValue() int {
	return t.next.value
}

// NewTile creates a new Tile object.
func NewTile(value int, x, y int) *Tile {
	return &Tile{
		current: TileData{
			value: value,
			x:     x,
			y:     y,
		},
		startPoppingCount: maxPoppingCount,
	}
}

// IsMoving returns a boolean value indicating if the tile is animating.
func (t *Tile) IsMoving() bool {
	return 0 < t.movingCount
}

func (t *Tile) stopAnimation() {
	if 0 < t.movingCount {
		t.current = t.next
		t.next = TileData{}
	}
	t.movingCount = 0
	t.startPoppingCount = 0
	t.poppingCount = 0
}

func tileAt(tiles map[*Tile]struct{}, x, y int) *Tile {
	var result *Tile
	for t := range tiles {
		if t.current.x != x || t.current.y != y {
			continue
		}
		if result != nil {
			panic("not reach")
		}
		result = t
	}
	return result
}

func currentOrNextTileAt(tiles map[*Tile]struct{}, x, y int) *Tile {
	var result *Tile
	for t := range tiles {
		if 0 < t.movingCount {
			if t.next.x != x || t.next.y != y || t.next.value == 0 {
				continue
			}
		} else {
			if t.current.x != x || t.current.y != y {
				continue
			}
		}
		if result != nil {
			panic("not reach")
		}
		result = t
	}
	return result
}

const (
	maxMovingCount  = 5
	maxPoppingCount = 6
)

// MoveTiles moves tiles in the given tiles map if possible.
// MoveTiles returns true if there are tiles that are to move, otherwise false.
//
// When MoveTiles is called, all tiles must not be about to move.
func MoveTiles(tiles map[*Tile]struct{}, size int, dir Dir) bool {
	vx, vy := dir.Vector()
	tx := []int{}
	ty := []int{}
	for i := 0; i < size; i++ {
		tx = append(tx, i)
		ty = append(ty, i)
	}
	if vx > 0 {
		sort.Sort(sort.Reverse(sort.IntSlice(tx)))
	}
	if vy > 0 {
		sort.Sort(sort.Reverse(sort.IntSlice(ty)))
	}

	moved := false
	for _, j := range ty {
		for _, i := range tx {
			t := tileAt(tiles, i, j)
			if t == nil {
				continue
			}
			if t.next != (TileData{}) {
				panic("not reach")
			}
			if t.IsMoving() {
				panic("not reach")
			}
			// (ii, jj) is the next position for tile t.
			// (ii, jj) is updated until a mergeable tile is found or
			// the tile t can't be moved any more.
			ii := i
			jj := j
			for {
				ni := ii + vx
				nj := jj + vy
				if ni < 0 || ni >= size || nj < 0 || nj >= size {
					break
				}
				tt := currentOrNextTileAt(tiles, ni, nj)
				if tt == nil {
					ii = ni
					jj = nj
					moved = true
					continue
				}
				if t.current.value != tt.current.value {
					break
				}
				if 0 < tt.movingCount && tt.current.value != tt.next.value {
					// tt is already being merged with another tile.
					// Break here without updating (ii, jj).
					break
				}
				ii = ni
				jj = nj
				moved = true
				break
			}
			// next is the next state of the tile t.
			next := TileData{}
			next.value = t.current.value
			// If there is a tile at the next position (ii, jj), this should be
			// mergeable. Let's merge.
			if tt := currentOrNextTileAt(tiles, ii, jj); tt != t && tt != nil {
				next.value = t.current.value + tt.current.value
				tt.next.value = 0
				tt.next.x = ii
				tt.next.y = jj
				tt.movingCount = maxMovingCount
			}
			next.x = ii
			next.y = jj
			if t.current != next {
				t.next = next
				t.movingCount = maxMovingCount
			}
		}
	}
	if !moved {
		for t := range tiles {
			t.next = TileData{}
			t.movingCount = 0
		}
	}
	return moved
}

func addRandomTile(tiles map[*Tile]struct{}, size int) error {
	cells := make([]bool, size*size)
	for t := range tiles {
		if t.IsMoving() {
			panic("not reach")
		}
		i := t.current.x + t.current.y*size
		cells[i] = true
	}
	availableCells := []int{}
	for i, b := range cells {
		if b {
			continue
		}
		availableCells = append(availableCells, i)
	}
	if len(availableCells) == 0 {
		return errors.New("twenty48: there is no space to add a new tile")
	}
	c := availableCells[rand.Intn(len(availableCells))]
	v := 2
	if rand.Intn(10) == 0 {
		v = 4
	}
	x := c % size
	y := c / size
	t := NewTile(v, x, y)
	tiles[t] = struct{}{}
	return nil
}

// Update updates the tile's animation states.
func (t *Tile) Update() error {
	switch {
	case 0 < t.movingCount:
		t.movingCount--
		if t.movingCount == 0 {
			if t.current.value != t.next.value && 0 < t.next.value {
				t.poppingCount = maxPoppingCount
			}
			t.current = t.next
			t.next = TileData{}
		}
	case 0 < t.startPoppingCount:
		t.startPoppingCount--
	case 0 < t.poppingCount:
		t.poppingCount--
	}
	return nil
}

func colorToScale(clr color.Color) (float64, float64, float64, float64) {
	r, g, b, a := clr.RGBA()
	rf := float64(r) / 0xffff
	gf := float64(g) / 0xffff
	bf := float64(b) / 0xffff
	af := float64(a) / 0xffff
	// Convert to non-premultiplied alpha components.
	if 0 < af {
		rf /= af
		gf /= af
		bf /= af
	}
	return rf, gf, bf, af
}

func mean(a, b int, rate float64) int {
	return int(float64(a)*(1-rate) + float64(b)*rate)
}

func meanF(a, b float64, rate float64) float64 {
	return a*(1-rate) + b*rate
}

const (
	tileSize   = 80
	tileMargin = 4
)

var (
	tileImage *ebiten.Image
)

func init() {
	tileImage, _ = ebiten.NewImage(tileSize, tileSize, ebiten.FilterDefault)
	tileImage.Fill(color.White)
}

// Draw draws the current tile to the given boardImage.
func (t *Tile) Draw(boardImage *ebiten.Image) {
	i, j := t.current.x, t.current.y
	ni, nj := t.next.x, t.next.y
	v := t.current.value
	if v == 0 {
		return
	}
	op := &ebiten.DrawImageOptions{}
	x := i*tileSize + (i+1)*tileMargin
	y := j*tileSize + (j+1)*tileMargin
	nx := ni*tileSize + (ni+1)*tileMargin
	ny := nj*tileSize + (nj+1)*tileMargin
	switch {
	case 0 < t.movingCount:
		rate := 1 - float64(t.movingCount)/maxMovingCount
		x = mean(x, nx, rate)
		y = mean(y, ny, rate)
	case 0 < t.startPoppingCount:
		rate := 1 - float64(t.startPoppingCount)/float64(maxPoppingCount)
		scale := meanF(0.0, 1.0, rate)
		op.GeoM.Translate(float64(-tileSize/2), float64(-tileSize/2))
		op.GeoM.Scale(scale, scale)
		op.GeoM.Translate(float64(tileSize/2), float64(tileSize/2))
	case 0 < t.poppingCount:
		const maxScale = 1.2
		rate := 0.0
		if maxPoppingCount*2/3 <= t.poppingCount {
			// 0 to 1
			rate = 1 - float64(t.poppingCount-2*maxPoppingCount/3)/float64(maxPoppingCount/3)
		} else {
			// 1 to 0
			rate = float64(t.poppingCount) / float64(maxPoppingCount*2/3)
		}
		scale := meanF(1.0, maxScale, rate)
		op.GeoM.Translate(float64(-tileSize/2), float64(-tileSize/2))
		op.GeoM.Scale(scale, scale)
		op.GeoM.Translate(float64(tileSize/2), float64(tileSize/2))
	}
	op.GeoM.Translate(float64(x), float64(y))
	r, g, b, a := colorToScale(tileBackgroundColor(v))
	op.ColorM.Scale(r, g, b, a)
	boardImage.DrawImage(tileImage, op)
	str := strconv.Itoa(v)

	f := mplusBigFont
	switch {
	case 3 < len(str):
		f = mplusSmallFont
	case 2 < len(str):
		f = mplusNormalFont
	}

	bound, _ := font.BoundString(f, str)
	w := (bound.Max.X - bound.Min.X).Ceil()
	h := (bound.Max.Y - bound.Min.Y).Ceil()
	x = x + (tileSize-w)/2
	y = y + (tileSize-h)/2 + h
	text.Draw(boardImage, str, f, x, y, tileColor(v))
}
<commit_msg>Revert "examples/2048: Use golang.org/x/image/font/opentype"<commit_after>// Copyright 2016 The Ebiten Authors
//
// 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 twenty48

import (
	"errors"
	"image/color"
	"log"
	"math/rand"
	"sort"
	"strconv"

	"github.com/golang/freetype/truetype"
	"golang.org/x/image/font"

	"github.com/hajimehoshi/ebiten"
	"github.com/hajimehoshi/ebiten/examples/resources/fonts"
	"github.com/hajimehoshi/ebiten/text"
)

var (
	mplusSmallFont  font.Face
	mplusNormalFont font.Face
	mplusBigFont    font.Face
)

func init() {
	tt, err := truetype.Parse(fonts.MPlus1pRegular_ttf)
	if err != nil {
		log.Fatal(err)
	}

	const dpi = 72
	mplusSmallFont = truetype.NewFace(tt, &truetype.Options{
		Size:    24,
		DPI:     dpi,
		Hinting: font.HintingFull,
	})
	mplusNormalFont = truetype.NewFace(tt, &truetype.Options{
		Size:    32,
		DPI:     dpi,
		Hinting: font.HintingFull,
	})
	mplusBigFont = truetype.NewFace(tt, &truetype.Options{
		Size:    48,
		DPI:     dpi,
		Hinting: font.HintingFull,
	})
}

// TileData represents a tile information like a value and a position.
type TileData struct {
	value int
	x     int
	y     int
}

// Tile represents a tile information including TileData and animation states.
type Tile struct {
	current TileData

	// next represents a next tile information after moving.
	// next is empty when the tile is not about to move.
	next TileData

	movingCount       int
	startPoppingCount int
	poppingCount      int
}

// Pos returns the tile's current position.
// Pos is used only at testing so far.
func (t *Tile) Pos() (int, int) {
	return t.current.x, t.current.y
}

// NextPos returns the tile's next position.
// NextPos is used only at testing so far.
func (t *Tile) NextPos() (int, int) {
	return t.next.x, t.next.y
}

// Value returns the tile's current value.
// Value is used only at testing so far.
func (t *Tile) Value() int {
	return t.current.value
}

// NextValue returns the tile's current value.
// NextValue is used only at testing so far.
func (t *Tile) NextValue() int {
	return t.next.value
}

// NewTile creates a new Tile object.
func NewTile(value int, x, y int) *Tile {
	return &Tile{
		current: TileData{
			value: value,
			x:     x,
			y:     y,
		},
		startPoppingCount: maxPoppingCount,
	}
}

// IsMoving returns a boolean value indicating if the tile is animating.
func (t *Tile) IsMoving() bool {
	return 0 < t.movingCount
}

func (t *Tile) stopAnimation() {
	if 0 < t.movingCount {
		t.current = t.next
		t.next = TileData{}
	}
	t.movingCount = 0
	t.startPoppingCount = 0
	t.poppingCount = 0
}

func tileAt(tiles map[*Tile]struct{}, x, y int) *Tile {
	var result *Tile
	for t := range tiles {
		if t.current.x != x || t.current.y != y {
			continue
		}
		if result != nil {
			panic("not reach")
		}
		result = t
	}
	return result
}

func currentOrNextTileAt(tiles map[*Tile]struct{}, x, y int) *Tile {
	var result *Tile
	for t := range tiles {
		if 0 < t.movingCount {
			if t.next.x != x || t.next.y != y || t.next.value == 0 {
				continue
			}
		} else {
			if t.current.x != x || t.current.y != y {
				continue
			}
		}
		if result != nil {
			panic("not reach")
		}
		result = t
	}
	return result
}

const (
	maxMovingCount  = 5
	maxPoppingCount = 6
)

// MoveTiles moves tiles in the given tiles map if possible.
// MoveTiles returns true if there are tiles that are to move, otherwise false.
//
// When MoveTiles is called, all tiles must not be about to move.
func MoveTiles(tiles map[*Tile]struct{}, size int, dir Dir) bool {
	vx, vy := dir.Vector()
	tx := []int{}
	ty := []int{}
	for i := 0; i < size; i++ {
		tx = append(tx, i)
		ty = append(ty, i)
	}
	if vx > 0 {
		sort.Sort(sort.Reverse(sort.IntSlice(tx)))
	}
	if vy > 0 {
		sort.Sort(sort.Reverse(sort.IntSlice(ty)))
	}

	moved := false
	for _, j := range ty {
		for _, i := range tx {
			t := tileAt(tiles, i, j)
			if t == nil {
				continue
			}
			if t.next != (TileData{}) {
				panic("not reach")
			}
			if t.IsMoving() {
				panic("not reach")
			}
			// (ii, jj) is the next position for tile t.
			// (ii, jj) is updated until a mergeable tile is found or
			// the tile t can't be moved any more.
			ii := i
			jj := j
			for {
				ni := ii + vx
				nj := jj + vy
				if ni < 0 || ni >= size || nj < 0 || nj >= size {
					break
				}
				tt := currentOrNextTileAt(tiles, ni, nj)
				if tt == nil {
					ii = ni
					jj = nj
					moved = true
					continue
				}
				if t.current.value != tt.current.value {
					break
				}
				if 0 < tt.movingCount && tt.current.value != tt.next.value {
					// tt is already being merged with another tile.
					// Break here without updating (ii, jj).
					break
				}
				ii = ni
				jj = nj
				moved = true
				break
			}
			// next is the next state of the tile t.
			next := TileData{}
			next.value = t.current.value
			// If there is a tile at the next position (ii, jj), this should be
			// mergeable. Let's merge.
			if tt := currentOrNextTileAt(tiles, ii, jj); tt != t && tt != nil {
				next.value = t.current.value + tt.current.value
				tt.next.value = 0
				tt.next.x = ii
				tt.next.y = jj
				tt.movingCount = maxMovingCount
			}
			next.x = ii
			next.y = jj
			if t.current != next {
				t.next = next
				t.movingCount = maxMovingCount
			}
		}
	}
	if !moved {
		for t := range tiles {
			t.next = TileData{}
			t.movingCount = 0
		}
	}
	return moved
}

func addRandomTile(tiles map[*Tile]struct{}, size int) error {
	cells := make([]bool, size*size)
	for t := range tiles {
		if t.IsMoving() {
			panic("not reach")
		}
		i := t.current.x + t.current.y*size
		cells[i] = true
	}
	availableCells := []int{}
	for i, b := range cells {
		if b {
			continue
		}
		availableCells = append(availableCells, i)
	}
	if len(availableCells) == 0 {
		return errors.New("twenty48: there is no space to add a new tile")
	}
	c := availableCells[rand.Intn(len(availableCells))]
	v := 2
	if rand.Intn(10) == 0 {
		v = 4
	}
	x := c % size
	y := c / size
	t := NewTile(v, x, y)
	tiles[t] = struct{}{}
	return nil
}

// Update updates the tile's animation states.
func (t *Tile) Update() error {
	switch {
	case 0 < t.movingCount:
		t.movingCount--
		if t.movingCount == 0 {
			if t.current.value != t.next.value && 0 < t.next.value {
				t.poppingCount = maxPoppingCount
			}
			t.current = t.next
			t.next = TileData{}
		}
	case 0 < t.startPoppingCount:
		t.startPoppingCount--
	case 0 < t.poppingCount:
		t.poppingCount--
	}
	return nil
}

func colorToScale(clr color.Color) (float64, float64, float64, float64) {
	r, g, b, a := clr.RGBA()
	rf := float64(r) / 0xffff
	gf := float64(g) / 0xffff
	bf := float64(b) / 0xffff
	af := float64(a) / 0xffff
	// Convert to non-premultiplied alpha components.
	if 0 < af {
		rf /= af
		gf /= af
		bf /= af
	}
	return rf, gf, bf, af
}

func mean(a, b int, rate float64) int {
	return int(float64(a)*(1-rate) + float64(b)*rate)
}

func meanF(a, b float64, rate float64) float64 {
	return a*(1-rate) + b*rate
}

const (
	tileSize   = 80
	tileMargin = 4
)

var (
	tileImage *ebiten.Image
)

func init() {
	tileImage, _ = ebiten.NewImage(tileSize, tileSize, ebiten.FilterDefault)
	tileImage.Fill(color.White)
}

// Draw draws the current tile to the given boardImage.
func (t *Tile) Draw(boardImage *ebiten.Image) {
	i, j := t.current.x, t.current.y
	ni, nj := t.next.x, t.next.y
	v := t.current.value
	if v == 0 {
		return
	}
	op := &ebiten.DrawImageOptions{}
	x := i*tileSize + (i+1)*tileMargin
	y := j*tileSize + (j+1)*tileMargin
	nx := ni*tileSize + (ni+1)*tileMargin
	ny := nj*tileSize + (nj+1)*tileMargin
	switch {
	case 0 < t.movingCount:
		rate := 1 - float64(t.movingCount)/maxMovingCount
		x = mean(x, nx, rate)
		y = mean(y, ny, rate)
	case 0 < t.startPoppingCount:
		rate := 1 - float64(t.startPoppingCount)/float64(maxPoppingCount)
		scale := meanF(0.0, 1.0, rate)
		op.GeoM.Translate(float64(-tileSize/2), float64(-tileSize/2))
		op.GeoM.Scale(scale, scale)
		op.GeoM.Translate(float64(tileSize/2), float64(tileSize/2))
	case 0 < t.poppingCount:
		const maxScale = 1.2
		rate := 0.0
		if maxPoppingCount*2/3 <= t.poppingCount {
			// 0 to 1
			rate = 1 - float64(t.poppingCount-2*maxPoppingCount/3)/float64(maxPoppingCount/3)
		} else {
			// 1 to 0
			rate = float64(t.poppingCount) / float64(maxPoppingCount*2/3)
		}
		scale := meanF(1.0, maxScale, rate)
		op.GeoM.Translate(float64(-tileSize/2), float64(-tileSize/2))
		op.GeoM.Scale(scale, scale)
		op.GeoM.Translate(float64(tileSize/2), float64(tileSize/2))
	}
	op.GeoM.Translate(float64(x), float64(y))
	r, g, b, a := colorToScale(tileBackgroundColor(v))
	op.ColorM.Scale(r, g, b, a)
	boardImage.DrawImage(tileImage, op)
	str := strconv.Itoa(v)

	f := mplusBigFont
	switch {
	case 3 < len(str):
		f = mplusSmallFont
	case 2 < len(str):
		f = mplusNormalFont
	}

	bound, _ := font.BoundString(f, str)
	w := (bound.Max.X - bound.Min.X).Ceil()
	h := (bound.Max.Y - bound.Min.Y).Ceil()
	x = x + (tileSize-w)/2
	y = y + (tileSize-h)/2 + h
	text.Draw(boardImage, str, f, x, y, tileColor(v))
}
<|endoftext|>


<commit_before>package gcs

import (
	"bufio"
	"context"
	"encoding/json"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/Microsoft/opengcs/internal/storage"
	"github.com/Microsoft/opengcs/internal/storage/overlay"
	"github.com/Microsoft/opengcs/internal/storage/scsi"
	"github.com/Microsoft/opengcs/service/gcs/prot"
	oci "github.com/opencontainers/runtime-spec/specs-go"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

const (
	// baseFilesPath is the path in the utility VM containing all the files
	// that will be used as the base layer for containers.
	baseFilesPath = "/tmp/base/"

	// mappedDiskMountTimeout is the amount of time before
	// mountMappedVirtualDisks will give up trying to mount a device.
	mappedDiskMountTimeout = time.Second * 2
)

type mountSpec struct {
	Source     string
	FileSystem string
	Flags      uintptr
	Options    []string
}

const (
	// From mount(8): Don't load the journal on mounting.  Note that if the
	// filesystem was not unmounted cleanly, skipping the journal replay will
	// lead to the filesystem containing inconsistencies that can lead to any
	// number of problems.
	mountOptionNoLoad = "noload"
	// Enable DAX mode. This turns off the local cache for the file system and
	// accesses the storage directly from host memory, reducing memory use
	// and increasing sharing across VMs. Only supported on vPMEM devices.
	mountOptionDax = "dax"

	// For now the file system is hard-coded
	defaultFileSystem = "ext4"
)

// Mount mounts the file system to the specified target.
func (ms *mountSpec) Mount(target string) error {
	options := strings.Join(ms.Options, ",")
	err := syscall.Mount(ms.Source, target, ms.FileSystem, ms.Flags, options)
	if err != nil {
		return errors.Wrapf(err, "mount %s %s %s 0x%x %s", ms.Source, target, ms.FileSystem, ms.Flags, options)
	}
	return nil
}

// MountWithTimedRetry attempts mounting multiple times up until the given
// timout. This is necessary because there is a span of time between when the
// device name becomes available under /sys/bus/scsi and when it appears under
// /dev. Once it appears under /dev, there is still a span of time before it
// becomes mountable. Retrying mount should succeed in mounting the device as
// long as it becomes mountable under /dev before the timeout.
func (ms *mountSpec) MountWithTimedRetry(target string) error {
	startTime := time.Now()
	for {
		err := ms.Mount(target)
		if err != nil {
			if time.Since(startTime) > mappedDiskMountTimeout {
				return errors.Wrapf(err, "failed to mount directory %s for mapped virtual disk device %s", target, ms.Source)
			}
		} else {
			break
		}
		time.Sleep(time.Millisecond * 10)
	}
	return nil
}

// getLayerMounts computes the mount specs for the scratch and layers.
func (c *gcsCore) getLayerMounts(scratch string, layers []prot.Layer) (scratchMount *mountSpec, layerMounts []*mountSpec, err error) {
	layerMounts = make([]*mountSpec, len(layers))
	for i, layer := range layers {
		deviceName, pmem, err := c.deviceIDToName(layer.Path)
		if err != nil {
			return nil, nil, err
		}
		options := []string{mountOptionNoLoad}
		// TODO (dcantah): Add mountOptionDax when supported.
		layerMounts[i] = &mountSpec{
			Source:     deviceName,
			FileSystem: defaultFileSystem,
			Flags:      syscall.MS_RDONLY,
			Options:    options,
		}
	}
	// An empty scratch value indicates no scratch space is to be attached.
	if scratch != "" {
		scratchDevice, _, err := c.deviceIDToName(scratch)
		if err != nil {
			return nil, nil, err
		}
		scratchMount = &mountSpec{
			Source:     scratchDevice,
			FileSystem: defaultFileSystem,
		}
	}

	return scratchMount, layerMounts, nil
}

// getMappedVirtualDiskMounts uses the Lun values in the given disks to
// retrieve their associated mount spec.
func (c *gcsCore) getMappedVirtualDiskMounts(disks []prot.MappedVirtualDisk) ([]*mountSpec, error) {
	devices := make([]*mountSpec, len(disks))
	for i, disk := range disks {
		device, err := c.scsiLunToName(disk.Lun)
		if err != nil {
			return nil, errors.Wrapf(err, "failed to get device name for mapped virtual disk %s, lun %d", disk.ContainerPath, disk.Lun)
		}
		flags := uintptr(0)
		var options []string
		if disk.ReadOnly {
			flags |= syscall.MS_RDONLY
			options = append(options, mountOptionNoLoad)
		}
		devices[i] = &mountSpec{
			Source:     device,
			FileSystem: defaultFileSystem,
			Flags:      flags,
			Options:    options,
		}
	}
	return devices, nil
}

// scsiLunToName finds the SCSI device with the given LUN. This assumes
// only one SCSI controller.
func (c *gcsCore) scsiLunToName(lun uint8) (string, error) {
	resolveCtx, cancel := context.WithTimeout(context.Background(), time.Second*2)
	defer cancel()
	return scsi.ControllerLunToName(resolveCtx, 0, lun)
}

// deviceIDToName converts a device ID (scsi:<lun> or pmem:<device#> to a
// device name (/dev/sd? or /dev/pmem?).
// For temporary compatibility, this also accepts just <lun> for SCSI devices.
func (c *gcsCore) deviceIDToName(id string) (device string, pmem bool, err error) {
	const (
		pmemPrefix = "pmem:"
		scsiPrefix = "scsi:"
	)

	if strings.HasPrefix(id, pmemPrefix) {
		return "/dev/pmem" + id[len(pmemPrefix):], true, nil
	}

	lunStr := id
	if strings.HasPrefix(id, scsiPrefix) {
		lunStr = id[len(scsiPrefix):]
	}

	if lun, err := strconv.ParseInt(lunStr, 10, 8); err == nil {
		name, err := c.scsiLunToName(uint8(lun))
		return name, false, err
	}

	return "", false, errors.Errorf("unknown device ID %s", id)
}

// mountMappedVirtualDisks mounts the given disks to the given directories,
// with the given options. The device names of each disk are given in a
// parallel slice.
func (c *gcsCore) mountMappedVirtualDisks(disks []prot.MappedVirtualDisk, mounts []*mountSpec) error {
	if len(disks) != len(mounts) {
		return errors.Errorf("disk and device slices were of different sizes. disks: %d, mounts: %d", len(disks), len(mounts))
	}
	for i, disk := range disks {
		// Don't mount the disk if AttachOnly is specified.
		if !disk.AttachOnly {
			if !disk.CreateInUtilityVM {
				return errors.New("we do not currently support mapping virtual disks inside the container namespace")
			}
			mount := mounts[i]
			if err := os.MkdirAll(disk.ContainerPath, 0700); err != nil {
				return errors.Wrapf(err, "failed to create directory for mapped virtual disk %s", disk.ContainerPath)
			}

			if err := mount.MountWithTimedRetry(disk.ContainerPath); err != nil {
				return err
			}
		}
	}
	return nil
}

// mountLayers mounts each device into a mountpoint, and then layers them into a
// union filesystem in the given order.
// These mountpoints are all stored under a directory reserved for the container
// with the given index.
func (c *gcsCore) mountLayers(index uint32, scratchMount *mountSpec, layers []*mountSpec) error {
	layerPrefix, scratchPath, upperdirPath, workdirPath, rootfsPath := c.getUnioningPaths(index)

	logrus.Infof("layerPrefix=%s", layerPrefix)
	logrus.Infof("scratchPath:%s", scratchPath)
	logrus.Infof("upperdirPath:%s", upperdirPath)
	logrus.Infof("workdirPath=%s", workdirPath)
	logrus.Infof("rootfsPath=%s", rootfsPath)

	// Mount the layer devices.
	layerPaths := make([]string, len(layers)+1)
	for i, layer := range layers {
		layerPath := filepath.Join(layerPrefix, strconv.Itoa(i))
		logrus.Infof("layerPath: %s", layerPath)
		if err := os.MkdirAll(layerPath, 0700); err != nil {
			return errors.Wrapf(err, "failed to create directory for layer %s", layerPath)
		}
		if err := layer.Mount(layerPath); err != nil {
			return errors.Wrapf(err, "failed to mount layer directory %s", layerPath)
		}
		layerPaths[i+1] = layerPath
	}
	// TODO: The base path code may be temporary until a more permanent DNS
	// solution is reached.
	// NOTE: This should probably still always be kept, because otherwise
	// mounting will fail when no layer devices are attached. There should
	// always be at least one layer, even if it's empty, to prevent this
	// from happening.
	layerPaths[0] = baseFilesPath

	// Mount the layers into a union filesystem.
	if err := os.MkdirAll(baseFilesPath, 0700); err != nil {
		return errors.Wrapf(err, "failed to create directory for base files %s", baseFilesPath)
	}
	if err := os.MkdirAll(scratchPath, 0755); err != nil {
		return errors.Wrapf(err, "failed to create directory for scratch space %s", scratchPath)
	}
	if scratchMount != nil {
		if err := scratchMount.Mount(scratchPath); err != nil {
			return errors.Wrapf(err, "failed to mount scratch directory %s", scratchPath)
		}
	} else {
		// NOTE: V1 has never supported a readonly overlay mount. It was "by
		// accident" always getting a writable overlay. Do nothing here if the
		// call does not have a scratch path.
	}
	return overlay.Mount(context.Background(), layerPaths, upperdirPath, workdirPath, rootfsPath, false)
}

// unmountLayers unmounts the union filesystem for the container with the given
// ID, as well as any devices whose mountpoints were layers in that filesystem.
func (c *gcsCore) unmountLayers(index uint32) error {
	layerPrefix, scratchPath, _, _, rootfsPath := c.getUnioningPaths(index)

	// clean up rootfsPath operations
	if err := storage.UnmountPath(context.Background(), rootfsPath, false); err != nil {
		return errors.Wrap(err, "failed to unmount root filesytem")
	}

	// clean up scratchPath operations
	if err := storage.UnmountPath(context.Background(), scratchPath, false); err != nil {
		return errors.Wrap(err, "failed to unmount scratch")
	}

	// Clean up layer path operations
	layerPaths, err := filepath.Glob(filepath.Join(layerPrefix, "*"))
	if err != nil {
		return errors.Wrap(err, "failed to get layer paths using Glob")
	}
	for _, layerPath := range layerPaths {
		if err := storage.UnmountPath(context.Background(), layerPath, false); err != nil {
			return errors.Wrap(err, "failed to unmount layer")
		}
	}

	return nil
}

// destroyContainerStorage removes any files the GCS stores on disk for the
// container with the given ID.
// These files include directories used for mountpoints in the union filesystem
// and config files.
func (c *gcsCore) destroyContainerStorage(index uint32) error {
	if err := os.RemoveAll(c.getContainerStoragePath(index)); err != nil {
		return errors.Wrapf(err, "failed to remove container storage path for container %s", c.getContainerIDFromIndex(index))
	}
	return nil
}

// writeConfigFile writes the given oci.Spec to disk so that it can be consumed
// by an OCI runtime.
func (c *gcsCore) writeConfigFile(index uint32, config *oci.Spec) error {
	if config == nil {
		return errors.New("failed to write init process config file, no options specified")
	}
	configPath := c.getConfigPath(index)
	if err := os.MkdirAll(filepath.Dir(configPath), 0700); err != nil {
		return errors.Wrapf(err, "failed to create config file directory for container %s", c.getContainerIDFromIndex(index))
	}
	configFile, err := os.Create(configPath)
	if err != nil {
		return errors.Wrapf(err, "failed to create config file for container %s", c.getContainerIDFromIndex(index))
	}
	defer configFile.Close()
	writer := bufio.NewWriter(configFile)
	if err := json.NewEncoder(writer).Encode(config); err != nil {
		return errors.Wrapf(err, "failed to write contents of config file for container %s", c.getContainerIDFromIndex(index))
	}
	if err := writer.Flush(); err != nil {
		return errors.Wrapf(err, "failed to flush to config file for container %s", c.getContainerIDFromIndex(index))
	}
	return nil
}

// getContainerStoragePath returns the path where the GCS stores files on disk
// for the container with the given index.
func (c *gcsCore) getContainerStoragePath(index uint32) string {
	return filepath.Join(c.baseStoragePath, strconv.FormatUint(uint64(index), 10))
}

// getUnioningPaths returns paths that will be used in the union filesystem for
// the container with the given index.
func (c *gcsCore) getUnioningPaths(index uint32) (layerPrefix string, scratchPath string, upperdirPath string, workdirPath string, rootfsPath string) {
	mountPath := c.getContainerStoragePath(index)
	layerPrefix = mountPath
	scratchPath = filepath.Join(mountPath, "scratch")
	upperdirPath = filepath.Join(mountPath, "scratch", "upper")
	workdirPath = filepath.Join(mountPath, "scratch", "work")
	rootfsPath = filepath.Join(mountPath, "rootfs")
	return
}

// getConfigPath returns the path to the container's config file.
func (c *gcsCore) getConfigPath(index uint32) string {
	return filepath.Join(c.getContainerStoragePath(index), "config.json")
}
<commit_msg>Fix pmem build error<commit_after>package gcs

import (
	"bufio"
	"context"
	"encoding/json"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/Microsoft/opengcs/internal/storage"
	"github.com/Microsoft/opengcs/internal/storage/overlay"
	"github.com/Microsoft/opengcs/internal/storage/scsi"
	"github.com/Microsoft/opengcs/service/gcs/prot"
	oci "github.com/opencontainers/runtime-spec/specs-go"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

const (
	// baseFilesPath is the path in the utility VM containing all the files
	// that will be used as the base layer for containers.
	baseFilesPath = "/tmp/base/"

	// mappedDiskMountTimeout is the amount of time before
	// mountMappedVirtualDisks will give up trying to mount a device.
	mappedDiskMountTimeout = time.Second * 2
)

type mountSpec struct {
	Source     string
	FileSystem string
	Flags      uintptr
	Options    []string
}

const (
	// From mount(8): Don't load the journal on mounting.  Note that if the
	// filesystem was not unmounted cleanly, skipping the journal replay will
	// lead to the filesystem containing inconsistencies that can lead to any
	// number of problems.
	mountOptionNoLoad = "noload"
	// Enable DAX mode. This turns off the local cache for the file system and
	// accesses the storage directly from host memory, reducing memory use
	// and increasing sharing across VMs. Only supported on vPMEM devices.
	mountOptionDax = "dax"

	// For now the file system is hard-coded
	defaultFileSystem = "ext4"
)

// Mount mounts the file system to the specified target.
func (ms *mountSpec) Mount(target string) error {
	options := strings.Join(ms.Options, ",")
	err := syscall.Mount(ms.Source, target, ms.FileSystem, ms.Flags, options)
	if err != nil {
		return errors.Wrapf(err, "mount %s %s %s 0x%x %s", ms.Source, target, ms.FileSystem, ms.Flags, options)
	}
	return nil
}

// MountWithTimedRetry attempts mounting multiple times up until the given
// timout. This is necessary because there is a span of time between when the
// device name becomes available under /sys/bus/scsi and when it appears under
// /dev. Once it appears under /dev, there is still a span of time before it
// becomes mountable. Retrying mount should succeed in mounting the device as
// long as it becomes mountable under /dev before the timeout.
func (ms *mountSpec) MountWithTimedRetry(target string) error {
	startTime := time.Now()
	for {
		err := ms.Mount(target)
		if err != nil {
			if time.Since(startTime) > mappedDiskMountTimeout {
				return errors.Wrapf(err, "failed to mount directory %s for mapped virtual disk device %s", target, ms.Source)
			}
		} else {
			break
		}
		time.Sleep(time.Millisecond * 10)
	}
	return nil
}

// getLayerMounts computes the mount specs for the scratch and layers.
func (c *gcsCore) getLayerMounts(scratch string, layers []prot.Layer) (scratchMount *mountSpec, layerMounts []*mountSpec, err error) {
	layerMounts = make([]*mountSpec, len(layers))
	for i, layer := range layers {
		deviceName, _, err := c.deviceIDToName(layer.Path)
		if err != nil {
			return nil, nil, err
		}
		options := []string{mountOptionNoLoad}
		// TODO (dcantah): Add mountOptionDax when supported.
		layerMounts[i] = &mountSpec{
			Source:     deviceName,
			FileSystem: defaultFileSystem,
			Flags:      syscall.MS_RDONLY,
			Options:    options,
		}
	}
	// An empty scratch value indicates no scratch space is to be attached.
	if scratch != "" {
		scratchDevice, _, err := c.deviceIDToName(scratch)
		if err != nil {
			return nil, nil, err
		}
		scratchMount = &mountSpec{
			Source:     scratchDevice,
			FileSystem: defaultFileSystem,
		}
	}

	return scratchMount, layerMounts, nil
}

// getMappedVirtualDiskMounts uses the Lun values in the given disks to
// retrieve their associated mount spec.
func (c *gcsCore) getMappedVirtualDiskMounts(disks []prot.MappedVirtualDisk) ([]*mountSpec, error) {
	devices := make([]*mountSpec, len(disks))
	for i, disk := range disks {
		device, err := c.scsiLunToName(disk.Lun)
		if err != nil {
			return nil, errors.Wrapf(err, "failed to get device name for mapped virtual disk %s, lun %d", disk.ContainerPath, disk.Lun)
		}
		flags := uintptr(0)
		var options []string
		if disk.ReadOnly {
			flags |= syscall.MS_RDONLY
			options = append(options, mountOptionNoLoad)
		}
		devices[i] = &mountSpec{
			Source:     device,
			FileSystem: defaultFileSystem,
			Flags:      flags,
			Options:    options,
		}
	}
	return devices, nil
}

// scsiLunToName finds the SCSI device with the given LUN. This assumes
// only one SCSI controller.
func (c *gcsCore) scsiLunToName(lun uint8) (string, error) {
	resolveCtx, cancel := context.WithTimeout(context.Background(), time.Second*2)
	defer cancel()
	return scsi.ControllerLunToName(resolveCtx, 0, lun)
}

// deviceIDToName converts a device ID (scsi:<lun> or pmem:<device#> to a
// device name (/dev/sd? or /dev/pmem?).
// For temporary compatibility, this also accepts just <lun> for SCSI devices.
func (c *gcsCore) deviceIDToName(id string) (device string, pmem bool, err error) {
	const (
		pmemPrefix = "pmem:"
		scsiPrefix = "scsi:"
	)

	if strings.HasPrefix(id, pmemPrefix) {
		return "/dev/pmem" + id[len(pmemPrefix):], true, nil
	}

	lunStr := id
	if strings.HasPrefix(id, scsiPrefix) {
		lunStr = id[len(scsiPrefix):]
	}

	if lun, err := strconv.ParseInt(lunStr, 10, 8); err == nil {
		name, err := c.scsiLunToName(uint8(lun))
		return name, false, err
	}

	return "", false, errors.Errorf("unknown device ID %s", id)
}

// mountMappedVirtualDisks mounts the given disks to the given directories,
// with the given options. The device names of each disk are given in a
// parallel slice.
func (c *gcsCore) mountMappedVirtualDisks(disks []prot.MappedVirtualDisk, mounts []*mountSpec) error {
	if len(disks) != len(mounts) {
		return errors.Errorf("disk and device slices were of different sizes. disks: %d, mounts: %d", len(disks), len(mounts))
	}
	for i, disk := range disks {
		// Don't mount the disk if AttachOnly is specified.
		if !disk.AttachOnly {
			if !disk.CreateInUtilityVM {
				return errors.New("we do not currently support mapping virtual disks inside the container namespace")
			}
			mount := mounts[i]
			if err := os.MkdirAll(disk.ContainerPath, 0700); err != nil {
				return errors.Wrapf(err, "failed to create directory for mapped virtual disk %s", disk.ContainerPath)
			}

			if err := mount.MountWithTimedRetry(disk.ContainerPath); err != nil {
				return err
			}
		}
	}
	return nil
}

// mountLayers mounts each device into a mountpoint, and then layers them into a
// union filesystem in the given order.
// These mountpoints are all stored under a directory reserved for the container
// with the given index.
func (c *gcsCore) mountLayers(index uint32, scratchMount *mountSpec, layers []*mountSpec) error {
	layerPrefix, scratchPath, upperdirPath, workdirPath, rootfsPath := c.getUnioningPaths(index)

	logrus.Infof("layerPrefix=%s", layerPrefix)
	logrus.Infof("scratchPath:%s", scratchPath)
	logrus.Infof("upperdirPath:%s", upperdirPath)
	logrus.Infof("workdirPath=%s", workdirPath)
	logrus.Infof("rootfsPath=%s", rootfsPath)

	// Mount the layer devices.
	layerPaths := make([]string, len(layers)+1)
	for i, layer := range layers {
		layerPath := filepath.Join(layerPrefix, strconv.Itoa(i))
		logrus.Infof("layerPath: %s", layerPath)
		if err := os.MkdirAll(layerPath, 0700); err != nil {
			return errors.Wrapf(err, "failed to create directory for layer %s", layerPath)
		}
		if err := layer.Mount(layerPath); err != nil {
			return errors.Wrapf(err, "failed to mount layer directory %s", layerPath)
		}
		layerPaths[i+1] = layerPath
	}
	// TODO: The base path code may be temporary until a more permanent DNS
	// solution is reached.
	// NOTE: This should probably still always be kept, because otherwise
	// mounting will fail when no layer devices are attached. There should
	// always be at least one layer, even if it's empty, to prevent this
	// from happening.
	layerPaths[0] = baseFilesPath

	// Mount the layers into a union filesystem.
	if err := os.MkdirAll(baseFilesPath, 0700); err != nil {
		return errors.Wrapf(err, "failed to create directory for base files %s", baseFilesPath)
	}
	if err := os.MkdirAll(scratchPath, 0755); err != nil {
		return errors.Wrapf(err, "failed to create directory for scratch space %s", scratchPath)
	}
	if scratchMount != nil {
		if err := scratchMount.Mount(scratchPath); err != nil {
			return errors.Wrapf(err, "failed to mount scratch directory %s", scratchPath)
		}
	} else {
		// NOTE: V1 has never supported a readonly overlay mount. It was "by
		// accident" always getting a writable overlay. Do nothing here if the
		// call does not have a scratch path.
	}
	return overlay.Mount(context.Background(), layerPaths, upperdirPath, workdirPath, rootfsPath, false)
}

// unmountLayers unmounts the union filesystem for the container with the given
// ID, as well as any devices whose mountpoints were layers in that filesystem.
func (c *gcsCore) unmountLayers(index uint32) error {
	layerPrefix, scratchPath, _, _, rootfsPath := c.getUnioningPaths(index)

	// clean up rootfsPath operations
	if err := storage.UnmountPath(context.Background(), rootfsPath, false); err != nil {
		return errors.Wrap(err, "failed to unmount root filesytem")
	}

	// clean up scratchPath operations
	if err := storage.UnmountPath(context.Background(), scratchPath, false); err != nil {
		return errors.Wrap(err, "failed to unmount scratch")
	}

	// Clean up layer path operations
	layerPaths, err := filepath.Glob(filepath.Join(layerPrefix, "*"))
	if err != nil {
		return errors.Wrap(err, "failed to get layer paths using Glob")
	}
	for _, layerPath := range layerPaths {
		if err := storage.UnmountPath(context.Background(), layerPath, false); err != nil {
			return errors.Wrap(err, "failed to unmount layer")
		}
	}

	return nil
}

// destroyContainerStorage removes any files the GCS stores on disk for the
// container with the given ID.
// These files include directories used for mountpoints in the union filesystem
// and config files.
func (c *gcsCore) destroyContainerStorage(index uint32) error {
	if err := os.RemoveAll(c.getContainerStoragePath(index)); err != nil {
		return errors.Wrapf(err, "failed to remove container storage path for container %s", c.getContainerIDFromIndex(index))
	}
	return nil
}

// writeConfigFile writes the given oci.Spec to disk so that it can be consumed
// by an OCI runtime.
func (c *gcsCore) writeConfigFile(index uint32, config *oci.Spec) error {
	if config == nil {
		return errors.New("failed to write init process config file, no options specified")
	}
	configPath := c.getConfigPath(index)
	if err := os.MkdirAll(filepath.Dir(configPath), 0700); err != nil {
		return errors.Wrapf(err, "failed to create config file directory for container %s", c.getContainerIDFromIndex(index))
	}
	configFile, err := os.Create(configPath)
	if err != nil {
		return errors.Wrapf(err, "failed to create config file for container %s", c.getContainerIDFromIndex(index))
	}
	defer configFile.Close()
	writer := bufio.NewWriter(configFile)
	if err := json.NewEncoder(writer).Encode(config); err != nil {
		return errors.Wrapf(err, "failed to write contents of config file for container %s", c.getContainerIDFromIndex(index))
	}
	if err := writer.Flush(); err != nil {
		return errors.Wrapf(err, "failed to flush to config file for container %s", c.getContainerIDFromIndex(index))
	}
	return nil
}

// getContainerStoragePath returns the path where the GCS stores files on disk
// for the container with the given index.
func (c *gcsCore) getContainerStoragePath(index uint32) string {
	return filepath.Join(c.baseStoragePath, strconv.FormatUint(uint64(index), 10))
}

// getUnioningPaths returns paths that will be used in the union filesystem for
// the container with the given index.
func (c *gcsCore) getUnioningPaths(index uint32) (layerPrefix string, scratchPath string, upperdirPath string, workdirPath string, rootfsPath string) {
	mountPath := c.getContainerStoragePath(index)
	layerPrefix = mountPath
	scratchPath = filepath.Join(mountPath, "scratch")
	upperdirPath = filepath.Join(mountPath, "scratch", "upper")
	workdirPath = filepath.Join(mountPath, "scratch", "work")
	rootfsPath = filepath.Join(mountPath, "rootfs")
	return
}

// getConfigPath returns the path to the container's config file.
func (c *gcsCore) getConfigPath(index uint32) string {
	return filepath.Join(c.getContainerStoragePath(index), "config.json")
}
<|endoftext|>


<commit_before>package servicehttp

import (
	"context"
	"net/http"
	"strings"

	"github.com/Comcast/webpa-common/logging"
	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	gokithttp "github.com/go-kit/kit/transport/http"
)

// Redirect returns a go-kit EncodeResponseFunc that redirects to the instance hashed by the accessor.
// If the original requestURI is populated under the go-kit key ContextKeyRequestURI, it is appended to
// the hashed instance.
func Redirect(logger log.Logger, redirectCode int) gokithttp.EncodeResponseFunc {
	if logger == nil {
		logger = logging.DefaultLogger()
	}

	if redirectCode < 300 {
		redirectCode = http.StatusTemporaryRedirect
	}

	return func(ctx context.Context, rw http.ResponseWriter, response interface{}) error {
		var (
			instance      = response.(string)
			requestURI, _ = ctx.Value(gokithttp.ContextKeyRequestURI).(string)
		)

		if len(requestURI) > 0 {
			instance = instance + strings.TrimRight(requestURI, "/")
		}

		logger.Log(level.Key(), level.DebugValue(), logging.MessageKey(), "redirecting", "instance", instance)
		rw.Header().Set("Location", instance)
		rw.WriteHeader(redirectCode)
		return nil
	}
}
<commit_msg>use the contextual logger<commit_after>package servicehttp

import (
	"context"
	"net/http"
	"strings"

	"github.com/Comcast/webpa-common/logging"
	"github.com/go-kit/kit/log/level"
	gokithttp "github.com/go-kit/kit/transport/http"
)

// Redirect returns a go-kit EncodeResponseFunc that redirects to the instance hashed by the accessor.
// If the original requestURI is populated under the go-kit key ContextKeyRequestURI, it is appended to
// the hashed instance.
func Redirect(redirectCode int) gokithttp.EncodeResponseFunc {
	if redirectCode < 300 {
		redirectCode = http.StatusTemporaryRedirect
	}

	return func(ctx context.Context, rw http.ResponseWriter, response interface{}) error {
		var (
			logger        = logging.GetLogger(ctx)
			instance      = response.(string)
			requestURI, _ = ctx.Value(gokithttp.ContextKeyRequestURI).(string)
		)

		if len(requestURI) > 0 {
			instance = instance + strings.TrimRight(requestURI, "/")
		}

		logger.Log(level.Key(), level.DebugValue(), logging.MessageKey(), "redirecting", "instance", instance)
		rw.Header().Set("Location", instance)
		rw.WriteHeader(redirectCode)
		return nil
	}
}
<|endoftext|>