<commit_before>package jantar

import (
	"fmt"
	"github.com/howeyc/fsnotify"
	"html/template"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"reflect"
	"strconv"
	"strings"
	"time"
)

// TemplateManager hooks
const (
	TmBeforeParse  = iota
	TmBeforeRender = iota
	tmLast         = iota
)

// TemplateManager is responsible for loading, watching and rendering templates
type TemplateManager struct {
	hooks
	directory string
	watcher   *fsnotify.Watcher
	tmplFuncs template.FuncMap
	tmplList  *template.Template
}

func newTemplateManager(directory string) *TemplateManager {
	funcs := template.FuncMap{
		"antiClickjacking": func() template.HTML {
			return template.HTML("")
		},
		"set": func(args map[string]interface{}, key string, value interface{}) string {
			if args != nil {
				args[key] = value
			}
			return ""
		},
		"array": func(args ...interface{}) []interface{} {
			var ret []interface{}
			for _, arg := range args {
				ret = append(ret, arg)
			}
			return ret
		},
		"errorClass": func(errors []string) string {
			if errors != nil {
				return "has-error"
			}
			return ""
		},
		"toHtml": func(str string) template.HTML {
			return template.HTML(str)
		},
		"url": func(name string, args ...interface{}) string {
			router := GetModule(ModuleRouter).(*router)
			return router.getReverseURL(name, args)
		},
		"flash": func(args map[string]interface{}, key string) string {
			if flashMap, ok := args["flash"]; ok {
				return flashMap.(map[string]string)[key]
			}
			return ""
		},
		"since": func(t time.Time) string {
			seconds := int(time.Since(t).Seconds())
			if seconds < 60 {
				return "< 1 minute ago"
			} else if seconds < 60*2 {
				return "1 minute ago"
			} else if seconds < 60*60 {
				return fmt.Sprintf("%d minutes ago", seconds/60)
			} else if seconds < 60*60*2 {
				return "1 hour ago"
			} else if seconds < 60*60*24 {
				return fmt.Sprintf("%d hours ago", seconds/(60*60))
			} else if seconds < 60*60*24*2 {
				return "1 day ago"
			} else if seconds < 60*60*24*30 {
				return fmt.Sprintf("%d days ago", seconds/(60*60*24))
			} else if seconds < 60*60*24*30*2 {
				return "1 month ago"
			} else if seconds < 60*60*24*30*12 {
				return fmt.Sprintf("%d months ago", seconds/(60*60*24*30))
			}
			return "> 1 year ago"
		},
		"paginate": func(curPage int, nPages int, offset int, url string) template.HTML {
			if nPages < 2 {
				return template.HTML("")
			}

			result := "
"

			if curPage > 1 {
				result += "
«First
" +
					"
«
"
			}

			if curPage-offset > 1 {
				result += "
...
"
			}

			for i := curPage - offset; i < curPage+offset+1; i++ {
				if i > 0 && i <= nPages {
					if i == curPage {
						result += "
" + strconv.Itoa(i) + "
"
					} else {
						result += "
" + strconv.Itoa(i) + "
"
					}
				}
			}

			if curPage+offset < nPages {
				result += "
...
"
			}

			if curPage != nPages {
				result += "
»
" +
					"
Last»
"
			}
			return template.HTML(result + "
")
		},
	}

	tm := &TemplateManager{directory: directory, tmplFuncs: funcs}

	// register hooks
	tm.registerHook(TmBeforeParse, reflect.TypeOf(
		(func(*TemplateManager, string, *[]byte))(nil)))
	tm.registerHook(TmBeforeRender, reflect.TypeOf(
		(func(*http.Request, *TemplateManager, *template.Template, map[string]interface{}))(nil)))

	return tm
}

// watch listens for file events and reloads templates on changes
func (tm *TemplateManager) watch() {
	for {
		select {
		case ev := <-tm.watcher.Event:
			if !ev.IsRename() && filepath.Ext(ev.Name) == ".html" {
				Log.Debug("reloading templates")
				go tm.loadTemplates()
				return
			}
		case err := <-tm.watcher.Error:
			Log.Warningdf(JLData{"error": err}, "file watcher error")
			return
		}
	}
}

func (tm *TemplateManager) loadTemplates() error {
	var err error
	var templates *template.Template

	// close watcher if running
	if tm.watcher != nil {
		tm.watcher.Close()
	}

	// create a new watcher and start the watcher thread
	if tm.watcher, err = fsnotify.NewWatcher(); err != nil {
		return err
	}
	go tm.watch()

	// walk resursive through the template directory
	res := filepath.Walk(tm.directory, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if info.IsDir() {
			if strings.HasPrefix(info.Name(), ".") {
				return filepath.SkipDir
			}

			// add the current directory to the watcher
			if err = tm.watcher.Watch(path); err != nil {
				Log.Warningdf(JLData{"error": err.Error()}, "can't watch directory '%s'", path)
			}
			return nil
		}

		if strings.HasSuffix(info.Name(), ".html") {
			fdata, err := ioutil.ReadFile(path)
			if err != nil {
				return err
			}

			tmplName := strings.Replace(strings.ToLower(path[len(tm.directory)+1:]), "\\", "/", -1)

			// call BEFORE_PARSE hooks
			hooks := tm.getHooks(TmBeforeParse)
			for _, hook := range hooks {
				hook.(func(*TemplateManager, string, *[]byte))(tm, tmplName, &fdata)
			}

			// add the custom template functions to the first template
			if templates == nil {
				templates, err = template.New(tmplName).Funcs(tm.tmplFuncs).Parse(string(fdata))
			} else {
				_, err = templates.New(tmplName).Parse(string(fdata))
			}

			if err != nil {
				return err
			}
		}
		return nil
	})

	// no errors occured, override the old list
	if res == nil {
		tm.tmplList = templates
	}

	return res
}

func (tm *TemplateManager) getTemplate(name string) *template.Template {
	if tm.tmplList == nil {
		return nil
	}

	return tm.tmplList.Lookup(strings.ToLower(name))
}

// AddTmplFunc adds a template function with a given name and function pointer.
// Note: AddTmplFunc has no effect if called after the templates have been parsed.
func (tm *TemplateManager) AddTmplFunc(name string, fn interface{}) {
	tm.tmplFuncs[name] = fn
}

// RenderTemplate renders a template with the given name and arguments.
// Note: A Controller should call its Render function instead.
func (tm *TemplateManager) RenderTemplate(respw http.ResponseWriter, req *http.Request, name string, args map[string]interface{}) error {
	tmpl := tm.getTemplate(name)
	if tmpl == nil {
		return fmt.Errorf("can't find template '%s'", strings.ToLower(name))
	}

	// call BEFORE_RENDER hooks
	hooks := tm.getHooks(TmBeforeRender)
	for _, hook := range hooks {
		hook.(func(*http.Request, *TemplateManager, *template.Template, map[string]interface{}))(req, tm, tmpl, args)
	}

	if err := tmpl.Execute(respw, args); err != nil {
		return fmt.Errorf("failed to render template. Reason: %s", err.Error())
	}

	return nil
}
<commit_msg>preparing flash middleware<commit_after>package jantar

import (
	"fmt"
	"github.com/howeyc/fsnotify"
	"html/template"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"reflect"
	"strconv"
	"strings"
	"time"
)

// TemplateManager hooks
const (
	TmBeforeParse  = iota
	TmBeforeRender = iota
	tmLast         = iota
)

// TemplateManager is responsible for loading, watching and rendering templates
type TemplateManager struct {
	hooks
	directory string
	watcher   *fsnotify.Watcher
	tmplFuncs template.FuncMap
	tmplList  *template.Template
}

func newTemplateManager(directory string) *TemplateManager {
	funcs := template.FuncMap{
		"antiClickjacking": func() template.HTML {
			return template.HTML("")
		},
		"set": func(args map[string]interface{}, key string, value interface{}) string {
			if args != nil {
				args[key] = value
			}
			return ""
		},
		"array": func(args ...interface{}) []interface{} {
			var ret []interface{}
			for _, arg := range args {
				ret = append(ret, arg)
			}
			return ret
		},
		"errorClass": func(errors []string) string {
			if errors != nil {
				return "has-error"
			}
			return ""
		},
		"toHtml": func(str string) template.HTML {
			return template.HTML(str)
		},
		"url": func(name string, args ...interface{}) string {
			router := GetModule(ModuleRouter).(*router)
			return router.getReverseURL(name, args)
		},
		"since": func(t time.Time) string {
			seconds := int(time.Since(t).Seconds())
			if seconds < 60 {
				return "< 1 minute ago"
			} else if seconds < 60*2 {
				return "1 minute ago"
			} else if seconds < 60*60 {
				return fmt.Sprintf("%d minutes ago", seconds/60)
			} else if seconds < 60*60*2 {
				return "1 hour ago"
			} else if seconds < 60*60*24 {
				return fmt.Sprintf("%d hours ago", seconds/(60*60))
			} else if seconds < 60*60*24*2 {
				return "1 day ago"
			} else if seconds < 60*60*24*30 {
				return fmt.Sprintf("%d days ago", seconds/(60*60*24))
			} else if seconds < 60*60*24*30*2 {
				return "1 month ago"
			} else if seconds < 60*60*24*30*12 {
				return fmt.Sprintf("%d months ago", seconds/(60*60*24*30))
			}
			return "> 1 year ago"
		},
		"paginate": func(curPage int, nPages int, offset int, url string) template.HTML {
			if nPages < 2 {
				return template.HTML("")
			}

			result := "
"

			if curPage > 1 {
				result += "
«First
" +
					"
«
"
			}

			if curPage-offset > 1 {
				result += "
...
"
			}

			for i := curPage - offset; i < curPage+offset+1; i++ {
				if i > 0 && i <= nPages {
					if i == curPage {
						result += "
" + strconv.Itoa(i) + "
"
					} else {
						result += "
" + strconv.Itoa(i) + "
"
					}
				}
			}

			if curPage+offset < nPages {
				result += "
...
"
			}

			if curPage != nPages {
				result += "
»
" +
					"
Last»
"
			}
			return template.HTML(result + "
")
		},
	}

	tm := &TemplateManager{directory: directory, tmplFuncs: funcs}

	// register hooks
	tm.registerHook(TmBeforeParse, reflect.TypeOf(
		(func(*TemplateManager, string, *[]byte))(nil)))
	tm.registerHook(TmBeforeRender, reflect.TypeOf(
		(func(*http.Request, *TemplateManager, *template.Template, map[string]interface{}))(nil)))

	return tm
}

// watch listens for file events and reloads templates on changes
func (tm *TemplateManager) watch() {
	for {
		select {
		case ev := <-tm.watcher.Event:
			if !ev.IsRename() && filepath.Ext(ev.Name) == ".html" {
				Log.Debug("reloading templates")
				go tm.loadTemplates()
				return
			}
		case err := <-tm.watcher.Error:
			Log.Warningdf(JLData{"error": err}, "file watcher error")
			return
		}
	}
}

func (tm *TemplateManager) loadTemplates() error {
	var err error
	var templates *template.Template

	// close watcher if running
	if tm.watcher != nil {
		tm.watcher.Close()
	}

	// create a new watcher and start the watcher thread
	if tm.watcher, err = fsnotify.NewWatcher(); err != nil {
		return err
	}
	go tm.watch()

	// walk resursive through the template directory
	res := filepath.Walk(tm.directory, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if info.IsDir() {
			if strings.HasPrefix(info.Name(), ".") {
				return filepath.SkipDir
			}

			// add the current directory to the watcher
			if err = tm.watcher.Watch(path); err != nil {
				Log.Warningdf(JLData{"error": err.Error()}, "can't watch directory '%s'", path)
			}
			return nil
		}

		if strings.HasSuffix(info.Name(), ".html") {
			fdata, err := ioutil.ReadFile(path)
			if err != nil {
				return err
			}

			tmplName := strings.Replace(strings.ToLower(path[len(tm.directory)+1:]), "\\", "/", -1)

			// call BEFORE_PARSE hooks
			hooks := tm.getHooks(TmBeforeParse)
			for _, hook := range hooks {
				hook.(func(*TemplateManager, string, *[]byte))(tm, tmplName, &fdata)
			}

			// add the custom template functions to the first template
			if templates == nil {
				templates, err = template.New(tmplName).Funcs(tm.tmplFuncs).Parse(string(fdata))
			} else {
				_, err = templates.New(tmplName).Parse(string(fdata))
			}

			if err != nil {
				return err
			}
		}
		return nil
	})

	// no errors occured, override the old list
	if res == nil {
		tm.tmplList = templates
	}

	return res
}

func (tm *TemplateManager) getTemplate(name string) *template.Template {
	if tm.tmplList == nil {
		return nil
	}

	return tm.tmplList.Lookup(strings.ToLower(name))
}

// AddTmplFunc adds a template function with a given name and function pointer.
// Note: AddTmplFunc has no effect if called after the templates have been parsed.
func (tm *TemplateManager) AddTmplFunc(name string, fn interface{}) {
	tm.tmplFuncs[name] = fn
}

// RenderTemplate renders a template with the given name and arguments.
// Note: A Controller should call its Render function instead.
func (tm *TemplateManager) RenderTemplate(respw http.ResponseWriter, req *http.Request, name string, args map[string]interface{}) error {
	tmpl := tm.getTemplate(name)
	if tmpl == nil {
		return fmt.Errorf("can't find template '%s'", strings.ToLower(name))
	}

	// call BEFORE_RENDER hooks
	hooks := tm.getHooks(TmBeforeRender)
	for _, hook := range hooks {
		hook.(func(*http.Request, *TemplateManager, *template.Template, map[string]interface{}))(req, tm, tmpl, args)
	}

	if err := tmpl.Execute(respw, args); err != nil {
		return fmt.Errorf("failed to render template. Reason: %s", err.Error())
	}

	return nil
}
<|endoftext|>


<commit_before>package terminal

import (
    "fmt"
)

const CLEAR = "\033[H\033[2J\033[3J"

func Clear() {
    fmt.Println(CLEAR)
    MoveCursor(0, 0)
}

func MoveCursor(x int, y int) {
    fmt.Printf("\033[%d;%dH", x, y)
}

<commit_msg>updates<commit_after>package terminal

import (
    "fmt"
    "bufio"
    "os"
    "bytes"
)

const CLEAR = "\033[H\033[2J\033[3J"
const RESET = "\033[0m"

const BOLD = "\033[1m%s\033[0m"

var Output *bufio.Writer = bufio.NewWriter(os.Stdout)
var Screen *bytes.Buffer = new(bytes.Buffer)

func Clear() {
    fmt.Println(CLEAR)
    MoveCursor(0,0)
}

func MoveCursor(x int, y int) {
    fmt.Printf("\033[%d;%dH", x, y)
}

func Print(str string) {
    fmt.Println(Output, str)
    Output.Flush()
}

func Bold(str string) {
    fmt.Printf(BOLD, str+"\n")
}
<|endoftext|>


<commit_before>package procspy

import (
	"bytes"
	"io"
	"sync"
	"time"

	log "github.com/Sirupsen/logrus"

	"github.com/weaveworks/scope/probe/process"
)

const (
	initialRateLimitPeriod = 50 * time.Millisecond  // Read 20 * fdBlockSize file descriptors (/proc/PID/fd/*) per namespace per second
	maxRateLimitPeriod     = 500 * time.Millisecond // Read at least 2 * fdBlockSize file descriptors per namespace per second
	minRateLimitPeriod     = initialRateLimitPeriod
	fdBlockSize            = uint64(300) // Maximum number of /proc/PID/fd/* files to stat per rate-limit period
	// (as a rule of thumb going through each block should be more expensive than reading /proc/PID/tcp{,6})
	targetWalkTime = 10 * time.Second // Aim at walking all files in 10 seconds
)

type reader interface {
	getWalkedProcPid(buf *bytes.Buffer) (map[uint64]*Proc, error)
	stop()
}

type backgroundReader struct {
	stopc         chan struct{}
	mtx           sync.Mutex
	latestBuf     *bytes.Buffer
	latestSockets map[uint64]*Proc
}

// starts a rate-limited background goroutine to read the expensive files from
// proc.
func newBackgroundReader(walker process.Walker) reader {
	br := &backgroundReader{
		stopc:         make(chan struct{}),
		latestSockets: map[uint64]*Proc{},
	}
	go br.loop(walker)
	return br
}

func (br *backgroundReader) stop() {
	close(br.stopc)
}

func (br *backgroundReader) getWalkedProcPid(buf *bytes.Buffer) (map[uint64]*Proc, error) {
	br.mtx.Lock()
	defer br.mtx.Unlock()

	// Don't access latestBuf directly but create a reader. In this way,
	// the buffer will not be empty in the next call of getWalkedProcPid
	// and it can be copied again.
	_, err := io.Copy(buf, bytes.NewReader(br.latestBuf.Bytes()))

	return br.latestSockets, err
}

func (br *backgroundReader) loop(walker process.Walker) {
	var (
		begin           time.Time                      // when we started the last performWalk
		tickc           = time.After(time.Millisecond) // fire immediately
		walkc           chan walkResult                // initially nil, i.e. off
		rateLimitPeriod = initialRateLimitPeriod
		restInterval    time.Duration
		ticker          = time.NewTicker(rateLimitPeriod)
		pWalker         = newPidWalker(walker, ticker.C, fdBlockSize)
	)

	for {
		select {
		case <-tickc:
			tickc = nil                      // turn off until the next loop
			walkc = make(chan walkResult, 1) // turn on (need buffered so we don't leak performWalk)
			begin = time.Now()               // reset counter
			go performWalk(pWalker, walkc)   // do work

		case result := <-walkc:
			// Expose results
			br.mtx.Lock()
			br.latestBuf = result.buf
			br.latestSockets = result.sockets
			br.mtx.Unlock()

			// Schedule next walk and adjust its rate limit
			walkTime := time.Since(begin)
			rateLimitPeriod, restInterval = scheduleNextWalk(rateLimitPeriod, walkTime)
			ticker.Stop()
			ticker = time.NewTicker(rateLimitPeriod)
			pWalker.tickc = ticker.C

			walkc = nil                      // turn off until the next loop
			tickc = time.After(restInterval) // turn on

		case <-br.stopc:
			pWalker.stop()
			ticker.Stop()
			return // abort
		}
	}
}

type foregroundReader struct {
	stopc         chan struct{}
	latestBuf     *bytes.Buffer
	latestSockets map[uint64]*Proc
	ticker        *time.Ticker
}

// reads synchronously files from /proc
func newForegroundReader(walker process.Walker) reader {
	fr := &foregroundReader{
		stopc:         make(chan struct{}),
		latestSockets: map[uint64]*Proc{},
	}
	var (
		walkc   = make(chan walkResult)
		ticker  = time.NewTicker(time.Millisecond) // fire every millisecond
		pWalker = newPidWalker(walker, ticker.C, fdBlockSize)
	)

	go performWalk(pWalker, walkc)

	result := <-walkc
	fr.latestBuf = result.buf
	fr.latestSockets = result.sockets
	fr.ticker = ticker

	return fr
}

func (fr *foregroundReader) stop() {
	fr.ticker.Stop()
	close(fr.stopc)
}

func (fr *foregroundReader) getWalkedProcPid(buf *bytes.Buffer) (map[uint64]*Proc, error) {
	// Don't access latestBuf directly but create a reader. In this way,
	// the buffer will not be empty in the next call of getWalkedProcPid
	// and it can be copied again.
	_, err := io.Copy(buf, bytes.NewReader(fr.latestBuf.Bytes()))

	return fr.latestSockets, err
}

type walkResult struct {
	buf     *bytes.Buffer
	sockets map[uint64]*Proc
}

func performWalk(w pidWalker, c chan<- walkResult) {
	var (
		err    error
		result = walkResult{
			buf: bytes.NewBuffer(make([]byte, 0, 5000)),
		}
	)

	result.sockets, err = w.walk(result.buf)
	if err != nil {
		log.Errorf("background /proc reader: error walking /proc: %s", err)
		result.buf.Reset()
		result.sockets = nil
	}
	c <- result
}

// Adjust rate limit for next walk and calculate when it should be started
func scheduleNextWalk(rateLimitPeriod time.Duration, took time.Duration) (newRateLimitPeriod time.Duration, restInterval time.Duration) {
	log.Debugf("background /proc reader: full pass took %s", took)
	if float64(took)/float64(targetWalkTime) > 1.5 {
		log.Warnf(
			"background /proc reader: full pass took %s: 50%% more than expected (%s)",
			took,
			targetWalkTime,
		)
	}

	// Adjust rate limit to more-accurately meet the target walk time in next iteration
	newRateLimitPeriod = time.Duration(float64(targetWalkTime) / float64(took) * float64(rateLimitPeriod))
	if newRateLimitPeriod > maxRateLimitPeriod {
		newRateLimitPeriod = maxRateLimitPeriod
	} else if newRateLimitPeriod < minRateLimitPeriod {
		newRateLimitPeriod = minRateLimitPeriod
	}
	log.Debugf("background /proc reader: new rate limit period %s", newRateLimitPeriod)

	return newRateLimitPeriod, targetWalkTime - took
}
<commit_msg>reader_linux: only access latestBuf when set<commit_after>package procspy

import (
	"bytes"
	"io"
	"sync"
	"time"

	log "github.com/Sirupsen/logrus"

	"github.com/weaveworks/scope/probe/process"
)

const (
	initialRateLimitPeriod = 50 * time.Millisecond  // Read 20 * fdBlockSize file descriptors (/proc/PID/fd/*) per namespace per second
	maxRateLimitPeriod     = 500 * time.Millisecond // Read at least 2 * fdBlockSize file descriptors per namespace per second
	minRateLimitPeriod     = initialRateLimitPeriod
	fdBlockSize            = uint64(300) // Maximum number of /proc/PID/fd/* files to stat per rate-limit period
	// (as a rule of thumb going through each block should be more expensive than reading /proc/PID/tcp{,6})
	targetWalkTime = 10 * time.Second // Aim at walking all files in 10 seconds
)

type reader interface {
	getWalkedProcPid(buf *bytes.Buffer) (map[uint64]*Proc, error)
	stop()
}

type backgroundReader struct {
	stopc         chan struct{}
	mtx           sync.Mutex
	latestBuf     *bytes.Buffer
	latestSockets map[uint64]*Proc
}

// starts a rate-limited background goroutine to read the expensive files from
// proc.
func newBackgroundReader(walker process.Walker) reader {
	br := &backgroundReader{
		stopc:         make(chan struct{}),
		latestSockets: map[uint64]*Proc{},
	}
	go br.loop(walker)
	return br
}

func (br *backgroundReader) stop() {
	close(br.stopc)
}

func (br *backgroundReader) getWalkedProcPid(buf *bytes.Buffer) (map[uint64]*Proc, error) {
	br.mtx.Lock()
	defer br.mtx.Unlock()

	var err error
	// Don't access latestBuf directly but create a reader. In this way,
	// the buffer will not be empty in the next call of getWalkedProcPid
	// and it can be copied again.
	if br.latestBuf != nil {
		_, err = io.Copy(buf, bytes.NewReader(br.latestBuf.Bytes()))
	}
	return br.latestSockets, err
}

func (br *backgroundReader) loop(walker process.Walker) {
	var (
		begin           time.Time                      // when we started the last performWalk
		tickc           = time.After(time.Millisecond) // fire immediately
		walkc           chan walkResult                // initially nil, i.e. off
		rateLimitPeriod = initialRateLimitPeriod
		restInterval    time.Duration
		ticker          = time.NewTicker(rateLimitPeriod)
		pWalker         = newPidWalker(walker, ticker.C, fdBlockSize)
	)

	for {
		select {
		case <-tickc:
			tickc = nil                      // turn off until the next loop
			walkc = make(chan walkResult, 1) // turn on (need buffered so we don't leak performWalk)
			begin = time.Now()               // reset counter
			go performWalk(pWalker, walkc)   // do work

		case result := <-walkc:
			// Expose results
			br.mtx.Lock()
			br.latestBuf = result.buf
			br.latestSockets = result.sockets
			br.mtx.Unlock()

			// Schedule next walk and adjust its rate limit
			walkTime := time.Since(begin)
			rateLimitPeriod, restInterval = scheduleNextWalk(rateLimitPeriod, walkTime)
			ticker.Stop()
			ticker = time.NewTicker(rateLimitPeriod)
			pWalker.tickc = ticker.C

			walkc = nil                      // turn off until the next loop
			tickc = time.After(restInterval) // turn on

		case <-br.stopc:
			pWalker.stop()
			ticker.Stop()
			return // abort
		}
	}
}

type foregroundReader struct {
	stopc         chan struct{}
	latestBuf     *bytes.Buffer
	latestSockets map[uint64]*Proc
	ticker        *time.Ticker
}

// reads synchronously files from /proc
func newForegroundReader(walker process.Walker) reader {
	fr := &foregroundReader{
		stopc:         make(chan struct{}),
		latestSockets: map[uint64]*Proc{},
	}
	var (
		walkc   = make(chan walkResult)
		ticker  = time.NewTicker(time.Millisecond) // fire every millisecond
		pWalker = newPidWalker(walker, ticker.C, fdBlockSize)
	)

	go performWalk(pWalker, walkc)

	result := <-walkc
	fr.latestBuf = result.buf
	fr.latestSockets = result.sockets
	fr.ticker = ticker

	return fr
}

func (fr *foregroundReader) stop() {
	fr.ticker.Stop()
	close(fr.stopc)
}

func (fr *foregroundReader) getWalkedProcPid(buf *bytes.Buffer) (map[uint64]*Proc, error) {
	// Don't access latestBuf directly but create a reader. In this way,
	// the buffer will not be empty in the next call of getWalkedProcPid
	// and it can be copied again.
	_, err := io.Copy(buf, bytes.NewReader(fr.latestBuf.Bytes()))

	return fr.latestSockets, err
}

type walkResult struct {
	buf     *bytes.Buffer
	sockets map[uint64]*Proc
}

func performWalk(w pidWalker, c chan<- walkResult) {
	var (
		err    error
		result = walkResult{
			buf: bytes.NewBuffer(make([]byte, 0, 5000)),
		}
	)

	result.sockets, err = w.walk(result.buf)
	if err != nil {
		log.Errorf("background /proc reader: error walking /proc: %s", err)
		result.buf.Reset()
		result.sockets = nil
	}
	c <- result
}

// Adjust rate limit for next walk and calculate when it should be started
func scheduleNextWalk(rateLimitPeriod time.Duration, took time.Duration) (newRateLimitPeriod time.Duration, restInterval time.Duration) {
	log.Debugf("background /proc reader: full pass took %s", took)
	if float64(took)/float64(targetWalkTime) > 1.5 {
		log.Warnf(
			"background /proc reader: full pass took %s: 50%% more than expected (%s)",
			took,
			targetWalkTime,
		)
	}

	// Adjust rate limit to more-accurately meet the target walk time in next iteration
	newRateLimitPeriod = time.Duration(float64(targetWalkTime) / float64(took) * float64(rateLimitPeriod))
	if newRateLimitPeriod > maxRateLimitPeriod {
		newRateLimitPeriod = maxRateLimitPeriod
	} else if newRateLimitPeriod < minRateLimitPeriod {
		newRateLimitPeriod = minRateLimitPeriod
	}
	log.Debugf("background /proc reader: new rate limit period %s", newRateLimitPeriod)

	return newRateLimitPeriod, targetWalkTime - took
}
<|endoftext|>


<commit_before>// Tor pluggable transports library.
//
// Sample client usage:
//
// PtClientSetup([]string{"foo"})
// ln, err := startSocksListener()
// if err != nil {
// 	panic(err.Error())
// }
// PtCmethod("foo", "socks4", ln.Addr())
// PtCmethodsDone()
//
// Sample server usage:
//
// info := PtServerSetup([]string{"foo", "bar"})
// for _, bindAddr := range info.BindAddrs {
// 	ln, err := startListener(bindAddr.Addr)
// 	if err != nil {
// 		panic(err.Error())
// 	}
// 	PtSmethod(bindAddr.MethodName, ln.Addr())
// }
// PtSmethodsDone()

package main

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"net"
	"os"
	"strings"
)

func getenv(key string) string {
	return os.Getenv(key)
}

// Abort with an ENV-ERROR if the environment variable isn't set.
func getenvRequired(key string) string {
	value := os.Getenv(key)
	if value == "" {
		PtEnvError(fmt.Sprintf("no %s environment variable", key))
	}
	return value
}

// Escape a string so it contains no byte values over 127 and doesn't contain
// any of the characters '\x00', '\n', or '\\'.
func escape(s string) string {
	var buf bytes.Buffer
	for _, b := range []byte(s) {
		if b == '\n' {
			buf.WriteString("\\n")
		} else if b == '\\' {
			buf.WriteString("\\\\")
		} else if 0 < b && b < 128 {
			buf.WriteByte(b)
		} else {
			fmt.Fprintf(&buf, "\\x%02x", b)
		}
	}
	return buf.String()
}

// Print a pluggable transports protocol line to stdout. The line consists of an
// unescaped keyword, followed by any number of escaped strings.
func PtLine(keyword string, v ...string) {
	var buf bytes.Buffer
	buf.WriteString(keyword)
	for _, x := range v {
		buf.WriteString(" " + escape(x))
	}
	fmt.Println(buf.String())
}

// All of the Pt*Error functions call os.Exit(1).

// Emit an ENV-ERROR with explanation text.
func PtEnvError(msg string) {
	PtLine("ENV-ERROR", msg)
	os.Exit(1)
}

// Emit a VERSION-ERROR with explanation text.
func PtVersionError(msg string) {
	PtLine("VERSION-ERROR", msg)
	os.Exit(1)
}

// Emit a CMETHOD-ERROR with explanation text.
func PtCmethodError(methodName, msg string) {
	PtLine("CMETHOD-ERROR", methodName, msg)
	os.Exit(1)
}

// Emit an SMETHOD-ERROR with explanation text.
func PtSmethodError(methodName, msg string) {
	PtLine("SMETHOD-ERROR", methodName, msg)
	os.Exit(1)
}

// Emit a CMETHOD line. socks must be "socks4" or "socks5". Call this once for
// each listening client SOCKS port.
func PtCmethod(name string, socks string, addr net.Addr) {
	PtLine("CMETHOD", name, socks, addr.String())
}

// Emit a CMETHODS DONE line. Call this after opening all client listeners.
func PtCmethodsDone() {
	PtLine("CMETHODS", "DONE")
}

// Emit an SMETHOD line. Call this once for each listening server port.
func PtSmethod(name string, addr net.Addr) {
	PtLine("SMETHOD", name, addr.String())
}

// Emit an SMETHODS DONE line. Call this after opening all server listeners.
func PtSmethodsDone() {
	PtLine("SMETHODS", "DONE")
}

// Get a pluggable transports version offered by Tor and understood by us, if
// any. The only version we understand is "1". This function reads the
// environment variable TOR_PT_MANAGED_TRANSPORT_VER.
func getManagedTransportVer() string {
	const transportVersion = "1"
	for _, offered := range strings.Split(getenvRequired("TOR_PT_MANAGED_TRANSPORT_VER"), ",") {
		if offered == transportVersion {
			return offered
		}
	}
	return ""
}

// Get the intersection of the method names offered by Tor and those in
// methodNames. This function reads the environment variable
// TOR_PT_CLIENT_TRANSPORTS.
func getClientTransports(methodNames []string) []string {
	clientTransports := getenvRequired("TOR_PT_CLIENT_TRANSPORTS")
	if clientTransports == "*" {
		return methodNames
	}
	result := make([]string, 0)
	for _, requested := range strings.Split(clientTransports, ",") {
		for _, methodName := range methodNames {
			if requested == methodName {
				result = append(result, methodName)
				break
			}
		}
	}
	return result
}

// This structure is returned by PtClientSetup. It consists of a list of method
// names.
type PtClientInfo struct {
	MethodNames []string
}

// Check the client pluggable transports environments, emitting an error message
// and exiting the program if any error is encountered. Returns a subset of
// methodNames requested by Tor.
func PtClientSetup(methodNames []string) PtClientInfo {
	var info PtClientInfo

	ver := getManagedTransportVer()
	if ver == "" {
		PtVersionError("no-version")
	} else {
		PtLine("VERSION", ver)
	}

	info.MethodNames = getClientTransports(methodNames)
	if len(info.MethodNames) == 0 {
		PtCmethodsDone()
		os.Exit(1)
	}

	return info
}

// A combination of a method name and an address, as extracted from
// TOR_PT_SERVER_BINDADDR.
type PtBindAddr struct {
	MethodName string
	Addr       *net.TCPAddr
}

// Resolve an address string into a net.TCPAddr.
func resolveBindAddr(bindAddr string) (*net.TCPAddr, error) {
	addr, err := net.ResolveTCPAddr("tcp", bindAddr)
	if err == nil {
		return addr, nil
	}
	// Before the fixing of bug #7011, tor doesn't put brackets around IPv6
	// addresses. Split after the last colon, assuming it is a port
	// separator, and try adding the brackets.
	parts := strings.Split(bindAddr, ":")
	if len(parts) <= 2 {
		return nil, err
	}
	bindAddr = "[" + strings.Join(parts[:len(parts)-1], ":") + "]:" + parts[len(parts)-1]
	return net.ResolveTCPAddr("tcp", bindAddr)
}

// Return a new slice, the members of which are those members of addrs having a
// MethodName in methodsNames.
func filterBindAddrs(addrs []PtBindAddr, methodNames []string) []PtBindAddr {
	var result []PtBindAddr

	for _, ba := range addrs {
		for _, methodName := range methodNames {
			if ba.MethodName == methodName {
				result = append(result, ba)
				break
			}
		}
	}

	return result
}

// Return a map from method names to bind addresses. The map is the contents of
// TOR_PT_SERVER_BINDADDR, with keys filtered by TOR_PT_SERVER_TRANSPORTS, and
// further filtered by the methods in methodNames.
func getServerBindAddrs(methodNames []string) []PtBindAddr {
	var result []PtBindAddr

	// Get the list of all requested bindaddrs.
	var serverBindAddr = getenvRequired("TOR_PT_SERVER_BINDADDR")
	for _, spec := range strings.Split(serverBindAddr, ",") {
		var bindAddr PtBindAddr

		parts := strings.SplitN(spec, "-", 2)
		if len(parts) != 2 {
			PtEnvError(fmt.Sprintf("TOR_PT_SERVER_BINDADDR: %q: doesn't contain \"-\"", spec))
		}
		bindAddr.MethodName = parts[0]
		addr, err := resolveBindAddr(parts[1])
		if err != nil {
			PtEnvError(fmt.Sprintf("TOR_PT_SERVER_BINDADDR: %q: %s", spec, err.Error()))
		}
		bindAddr.Addr = addr
		result = append(result, bindAddr)
	}

	// Filter by TOR_PT_SERVER_TRANSPORTS.
	serverTransports := getenvRequired("TOR_PT_SERVER_TRANSPORTS")
	if serverTransports != "*" {
		result = filterBindAddrs(result, strings.Split(serverTransports, ","))
	}

	// Finally filter by what we understand.
	result = filterBindAddrs(result, methodNames)

	return result
}

// Reads and validates the contents of an auth cookie file. Returns the 32-byte
// cookie. See section 4.2.1.2 of pt-spec.txt.
func readAuthCookieFile(filename string) ([]byte, error) {
	authCookieHeader := []byte("! Extended ORPort Auth Cookie !\x0a")
	header := make([]byte, 32)
	cookie := make([]byte, 32)

	f, err := os.Open(filename)
	if err != nil {
		return cookie, err
	}
	defer f.Close()

	n, err := io.ReadFull(f, header)
	if err != nil {
		return cookie, err
	}
	n, err = io.ReadFull(f, cookie)
	if err != nil {
		return cookie, err
	}
	// Check that the file ends here.
	n, err = f.Read(make([]byte, 1))
	if n != 0 {
		return cookie, errors.New(fmt.Sprintf("file is longer than 64 bytes"))
	} else if err != io.EOF {
		return cookie, errors.New(fmt.Sprintf("did not find EOF at end of file"))
	}

	if !bytes.Equal(header, authCookieHeader) {
		return cookie, errors.New(fmt.Sprintf("missing auth cookie header"))
	}

	return cookie, nil
}

// This structure is returned by PtServerSetup. It consists of a list of
// PtBindAddrs, along with a single address for the ORPort.
type PtServerInfo struct {
	BindAddrs      []PtBindAddr
	OrAddr         *net.TCPAddr
	ExtendedOrAddr *net.TCPAddr
	AuthCookie     []byte
}

// Check the server pluggable transports environments, emitting an error message
// and exiting the program if any error is encountered. Resolves the various
// requested bind addresses and the server ORPort. Returns a PtServerInfo
// struct.
func PtServerSetup(methodNames []string) PtServerInfo {
	var info PtServerInfo
	var err error

	ver := getManagedTransportVer()
	if ver == "" {
		PtVersionError("no-version")
	} else {
		PtLine("VERSION", ver)
	}

	var orPort = getenvRequired("TOR_PT_ORPORT")
	info.OrAddr, err = net.ResolveTCPAddr("tcp", orPort)
	if err != nil {
		PtEnvError(fmt.Sprintf("cannot resolve TOR_PT_ORPORT %q: %s", orPort, err.Error()))
	}

	info.BindAddrs = getServerBindAddrs(methodNames)
	if len(info.BindAddrs) == 0 {
		PtSmethodsDone()
		os.Exit(1)
	}

	var extendedOrPort = getenv("TOR_PT_EXTENDED_SERVER_PORT")
	if extendedOrPort != "" {
		info.ExtendedOrAddr, err = net.ResolveTCPAddr("tcp", extendedOrPort)
		if err != nil {
			PtEnvError(fmt.Sprintf("cannot resolve TOR_PT_EXTENDED_SERVER_PORT %q: %s", extendedOrPort, err.Error()))
		}
	}

	var authCookieFilename = getenv("TOR_PT_AUTH_COOKIE_FILE")
	if authCookieFilename != "" {
		info.AuthCookie, err = readAuthCookieFile(authCookieFilename)
		if err != nil {
			PtEnvError(fmt.Sprintf("error reading TOR_PT_AUTH_COOKIE_FILE %q: %s", authCookieFilename, err.Error()))
		}
	}

	return info
}

// Connect to info.ExtendedOrAddr if defined, or else info.OrAddr, and return an
// open *net.TCPConn. If connecting to the extended OR port, extended OR port
// authentication à la 217-ext-orport-auth.txt is done before returning; an
// error is returned if authentication fails.
func PtConnectOr(info *PtServerInfo, conn net.Conn) (*net.TCPConn, error) {
	return net.DialTCP("tcp", nil, ptInfo.OrAddr)
}
<commit_msg>extended OR port authentication.<commit_after>// Tor pluggable transports library.
//
// Sample client usage:
//
// PtClientSetup([]string{"foo"})
// ln, err := startSocksListener()
// if err != nil {
// 	panic(err.Error())
// }
// PtCmethod("foo", "socks4", ln.Addr())
// PtCmethodsDone()
//
// Sample server usage:
//
// info := PtServerSetup([]string{"foo", "bar"})
// for _, bindAddr := range info.BindAddrs {
// 	ln, err := startListener(bindAddr.Addr)
// 	if err != nil {
// 		panic(err.Error())
// 	}
// 	PtSmethod(bindAddr.MethodName, ln.Addr())
// }
// PtSmethodsDone()

package main

import (
	"bufio"
	"bytes"
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"crypto/subtle"
	"errors"
	"fmt"
	"io"
	"net"
	"os"
	"strings"
)

func getenv(key string) string {
	return os.Getenv(key)
}

// Abort with an ENV-ERROR if the environment variable isn't set.
func getenvRequired(key string) string {
	value := os.Getenv(key)
	if value == "" {
		PtEnvError(fmt.Sprintf("no %s environment variable", key))
	}
	return value
}

// Escape a string so it contains no byte values over 127 and doesn't contain
// any of the characters '\x00', '\n', or '\\'.
func escape(s string) string {
	var buf bytes.Buffer
	for _, b := range []byte(s) {
		if b == '\n' {
			buf.WriteString("\\n")
		} else if b == '\\' {
			buf.WriteString("\\\\")
		} else if 0 < b && b < 128 {
			buf.WriteByte(b)
		} else {
			fmt.Fprintf(&buf, "\\x%02x", b)
		}
	}
	return buf.String()
}

// Print a pluggable transports protocol line to stdout. The line consists of an
// unescaped keyword, followed by any number of escaped strings.
func PtLine(keyword string, v ...string) {
	var buf bytes.Buffer
	buf.WriteString(keyword)
	for _, x := range v {
		buf.WriteString(" " + escape(x))
	}
	fmt.Println(buf.String())
}

// All of the Pt*Error functions call os.Exit(1).

// Emit an ENV-ERROR with explanation text.
func PtEnvError(msg string) {
	PtLine("ENV-ERROR", msg)
	os.Exit(1)
}

// Emit a VERSION-ERROR with explanation text.
func PtVersionError(msg string) {
	PtLine("VERSION-ERROR", msg)
	os.Exit(1)
}

// Emit a CMETHOD-ERROR with explanation text.
func PtCmethodError(methodName, msg string) {
	PtLine("CMETHOD-ERROR", methodName, msg)
	os.Exit(1)
}

// Emit an SMETHOD-ERROR with explanation text.
func PtSmethodError(methodName, msg string) {
	PtLine("SMETHOD-ERROR", methodName, msg)
	os.Exit(1)
}

// Emit a CMETHOD line. socks must be "socks4" or "socks5". Call this once for
// each listening client SOCKS port.
func PtCmethod(name string, socks string, addr net.Addr) {
	PtLine("CMETHOD", name, socks, addr.String())
}

// Emit a CMETHODS DONE line. Call this after opening all client listeners.
func PtCmethodsDone() {
	PtLine("CMETHODS", "DONE")
}

// Emit an SMETHOD line. Call this once for each listening server port.
func PtSmethod(name string, addr net.Addr) {
	PtLine("SMETHOD", name, addr.String())
}

// Emit an SMETHODS DONE line. Call this after opening all server listeners.
func PtSmethodsDone() {
	PtLine("SMETHODS", "DONE")
}

// Get a pluggable transports version offered by Tor and understood by us, if
// any. The only version we understand is "1". This function reads the
// environment variable TOR_PT_MANAGED_TRANSPORT_VER.
func getManagedTransportVer() string {
	const transportVersion = "1"
	for _, offered := range strings.Split(getenvRequired("TOR_PT_MANAGED_TRANSPORT_VER"), ",") {
		if offered == transportVersion {
			return offered
		}
	}
	return ""
}

// Get the intersection of the method names offered by Tor and those in
// methodNames. This function reads the environment variable
// TOR_PT_CLIENT_TRANSPORTS.
func getClientTransports(methodNames []string) []string {
	clientTransports := getenvRequired("TOR_PT_CLIENT_TRANSPORTS")
	if clientTransports == "*" {
		return methodNames
	}
	result := make([]string, 0)
	for _, requested := range strings.Split(clientTransports, ",") {
		for _, methodName := range methodNames {
			if requested == methodName {
				result = append(result, methodName)
				break
			}
		}
	}
	return result
}

// This structure is returned by PtClientSetup. It consists of a list of method
// names.
type PtClientInfo struct {
	MethodNames []string
}

// Check the client pluggable transports environments, emitting an error message
// and exiting the program if any error is encountered. Returns a subset of
// methodNames requested by Tor.
func PtClientSetup(methodNames []string) PtClientInfo {
	var info PtClientInfo

	ver := getManagedTransportVer()
	if ver == "" {
		PtVersionError("no-version")
	} else {
		PtLine("VERSION", ver)
	}

	info.MethodNames = getClientTransports(methodNames)
	if len(info.MethodNames) == 0 {
		PtCmethodsDone()
		os.Exit(1)
	}

	return info
}

// A combination of a method name and an address, as extracted from
// TOR_PT_SERVER_BINDADDR.
type PtBindAddr struct {
	MethodName string
	Addr       *net.TCPAddr
}

// Resolve an address string into a net.TCPAddr.
func resolveBindAddr(bindAddr string) (*net.TCPAddr, error) {
	addr, err := net.ResolveTCPAddr("tcp", bindAddr)
	if err == nil {
		return addr, nil
	}
	// Before the fixing of bug #7011, tor doesn't put brackets around IPv6
	// addresses. Split after the last colon, assuming it is a port
	// separator, and try adding the brackets.
	parts := strings.Split(bindAddr, ":")
	if len(parts) <= 2 {
		return nil, err
	}
	bindAddr = "[" + strings.Join(parts[:len(parts)-1], ":") + "]:" + parts[len(parts)-1]
	return net.ResolveTCPAddr("tcp", bindAddr)
}

// Return a new slice, the members of which are those members of addrs having a
// MethodName in methodsNames.
func filterBindAddrs(addrs []PtBindAddr, methodNames []string) []PtBindAddr {
	var result []PtBindAddr

	for _, ba := range addrs {
		for _, methodName := range methodNames {
			if ba.MethodName == methodName {
				result = append(result, ba)
				break
			}
		}
	}

	return result
}

// Return a map from method names to bind addresses. The map is the contents of
// TOR_PT_SERVER_BINDADDR, with keys filtered by TOR_PT_SERVER_TRANSPORTS, and
// further filtered by the methods in methodNames.
func getServerBindAddrs(methodNames []string) []PtBindAddr {
	var result []PtBindAddr

	// Get the list of all requested bindaddrs.
	var serverBindAddr = getenvRequired("TOR_PT_SERVER_BINDADDR")
	for _, spec := range strings.Split(serverBindAddr, ",") {
		var bindAddr PtBindAddr

		parts := strings.SplitN(spec, "-", 2)
		if len(parts) != 2 {
			PtEnvError(fmt.Sprintf("TOR_PT_SERVER_BINDADDR: %q: doesn't contain \"-\"", spec))
		}
		bindAddr.MethodName = parts[0]
		addr, err := resolveBindAddr(parts[1])
		if err != nil {
			PtEnvError(fmt.Sprintf("TOR_PT_SERVER_BINDADDR: %q: %s", spec, err.Error()))
		}
		bindAddr.Addr = addr
		result = append(result, bindAddr)
	}

	// Filter by TOR_PT_SERVER_TRANSPORTS.
	serverTransports := getenvRequired("TOR_PT_SERVER_TRANSPORTS")
	if serverTransports != "*" {
		result = filterBindAddrs(result, strings.Split(serverTransports, ","))
	}

	// Finally filter by what we understand.
	result = filterBindAddrs(result, methodNames)

	return result
}

// Reads and validates the contents of an auth cookie file. Returns the 32-byte
// cookie. See section 4.2.1.2 of pt-spec.txt.
func readAuthCookieFile(filename string) ([]byte, error) {
	authCookieHeader := []byte("! Extended ORPort Auth Cookie !\x0a")
	header := make([]byte, 32)
	cookie := make([]byte, 32)

	f, err := os.Open(filename)
	if err != nil {
		return cookie, err
	}
	defer f.Close()

	n, err := io.ReadFull(f, header)
	if err != nil {
		return cookie, err
	}
	n, err = io.ReadFull(f, cookie)
	if err != nil {
		return cookie, err
	}
	// Check that the file ends here.
	n, err = f.Read(make([]byte, 1))
	if n != 0 {
		return cookie, errors.New(fmt.Sprintf("file is longer than 64 bytes"))
	} else if err != io.EOF {
		return cookie, errors.New(fmt.Sprintf("did not find EOF at end of file"))
	}

	if !bytes.Equal(header, authCookieHeader) {
		return cookie, errors.New(fmt.Sprintf("missing auth cookie header"))
	}

	return cookie, nil
}

// This structure is returned by PtServerSetup. It consists of a list of
// PtBindAddrs, along with a single address for the ORPort.
type PtServerInfo struct {
	BindAddrs      []PtBindAddr
	OrAddr         *net.TCPAddr
	ExtendedOrAddr *net.TCPAddr
	AuthCookie     []byte
}

// Check the server pluggable transports environments, emitting an error message
// and exiting the program if any error is encountered. Resolves the various
// requested bind addresses and the server ORPort. Returns a PtServerInfo
// struct.
func PtServerSetup(methodNames []string) PtServerInfo {
	var info PtServerInfo
	var err error

	ver := getManagedTransportVer()
	if ver == "" {
		PtVersionError("no-version")
	} else {
		PtLine("VERSION", ver)
	}

	var orPort = getenvRequired("TOR_PT_ORPORT")
	info.OrAddr, err = net.ResolveTCPAddr("tcp", orPort)
	if err != nil {
		PtEnvError(fmt.Sprintf("cannot resolve TOR_PT_ORPORT %q: %s", orPort, err.Error()))
	}

	info.BindAddrs = getServerBindAddrs(methodNames)
	if len(info.BindAddrs) == 0 {
		PtSmethodsDone()
		os.Exit(1)
	}

	var extendedOrPort = getenv("TOR_PT_EXTENDED_SERVER_PORT")
	if extendedOrPort != "" {
		info.ExtendedOrAddr, err = net.ResolveTCPAddr("tcp", extendedOrPort)
		if err != nil {
			PtEnvError(fmt.Sprintf("cannot resolve TOR_PT_EXTENDED_SERVER_PORT %q: %s", extendedOrPort, err.Error()))
		}
	}

	var authCookieFilename = getenv("TOR_PT_AUTH_COOKIE_FILE")
	if authCookieFilename != "" {
		info.AuthCookie, err = readAuthCookieFile(authCookieFilename)
		if err != nil {
			PtEnvError(fmt.Sprintf("error reading TOR_PT_AUTH_COOKIE_FILE %q: %s", authCookieFilename, err.Error()))
		}
	}

	return info
}

// See 217-ext-orport-auth.txt section 4.2.1.3.
func computeServerHash(info *PtServerInfo, clientNonce, serverNonce []byte) []byte {
	h := hmac.New(sha256.New, info.AuthCookie)
	io.WriteString(h, "ExtORPort authentication server-to-client hash")
	h.Write(clientNonce)
	h.Write(serverNonce)
	return h.Sum([]byte{})
}

// See 217-ext-orport-auth.txt section 4.2.1.3.
func computeClientHash(info *PtServerInfo, clientNonce, serverNonce []byte) []byte {
	h := hmac.New(sha256.New, info.AuthCookie)
	io.WriteString(h, "ExtORPort authentication client-to-server hash")
	h.Write(clientNonce)
	h.Write(serverNonce)
	return h.Sum([]byte{})
}

func extOrPortAuthenticate(s *net.TCPConn, info *PtServerInfo) error {
	r := bufio.NewReader(s)

	// Read auth types. 217-ext-orport-auth.txt section 4.1.
	var authTypes [256]bool
	var count int
	for count = 0; count < 256; count++ {
		b, err := r.ReadByte()
		if err != nil {
			return err
		}
		if b == 0 {
			break
		}
		authTypes[b] = true
	}
	if count >= 256 {
		return errors.New(fmt.Sprintf("read 256 auth types without seeing \\x00"))
	}

	// We support only type 1, SAFE_COOKIE.
	if !authTypes[1] {
		return errors.New(fmt.Sprintf("server didn't offer auth type 1"))
	}
	_, err := s.Write([]byte{1})
	if err != nil {
		return err
	}

	clientNonce := make([]byte, 32)
	clientHash := make([]byte, 32)
	serverNonce := make([]byte, 32)
	serverHash := make([]byte, 32)

	_, err = io.ReadFull(rand.Reader, clientNonce)
	if err != nil {
		return err
	}
	_, err = s.Write(clientNonce)
	if err != nil {
		return err
	}

	_, err = io.ReadFull(r, serverHash)
	if err != nil {
		return err
	}
	_, err = io.ReadFull(r, serverNonce)
	if err != nil {
		return err
	}

	expectedServerHash := computeServerHash(info, clientNonce, serverNonce)
	if subtle.ConstantTimeCompare(serverHash, expectedServerHash) != 1 {
		return errors.New(fmt.Sprintf("mismatch in server hash"))
	}

	clientHash = computeClientHash(info, clientNonce, serverNonce)
	_, err = s.Write(clientHash)
	if err != nil {
		return err
	}

	status := make([]byte, 1)
	_, err = io.ReadFull(r, status)
	if err != nil {
		return err
	}
	if status[0] != 1 {
		return errors.New(fmt.Sprintf("server rejected authentication"))
	}

	if r.Buffered() != 0 {
		return errors.New(fmt.Sprintf("%d bytes left after extended OR port authentication", r.Buffered()))
	}

	return nil
}

// Connect to info.ExtendedOrAddr if defined, or else info.OrAddr, and return an
// open *net.TCPConn. If connecting to the extended OR port, extended OR port
// authentication à la 217-ext-orport-auth.txt is done before returning; an
// error is returned if authentication fails.
func PtConnectOr(info *PtServerInfo, conn net.Conn) (*net.TCPConn, error) {
	if info.ExtendedOrAddr == nil {
		return net.DialTCP("tcp", nil, info.OrAddr)
	}

	s, err := net.DialTCP("tcp", nil, info.ExtendedOrAddr)
	if err != nil {
		return nil, err
	}
	err = extOrPortAuthenticate(s, info)
	if err != nil {
		s.Close()
		return nil, err
	}

	return s, nil
}
<|endoftext|>


<commit_before>// +build linux darwin

package command

import (
	"fmt"
	"runtime"

	"bazil.org/fuse"
	"bazil.org/fuse/fs"
	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/util"
	"github.com/chrislusf/seaweedfs/weed/filesys"
)

func runMount(cmd *Command, args []string) bool {
	fmt.Printf("This is SeaweedFS version %s %s %s\n", util.VERSION, runtime.GOOS, runtime.GOARCH)
	if *mountOptions.dir == "" {
		fmt.Printf("Please specify the mount directory via \"-dir\"")
		return false
	}

	fuse.Unmount(*mountOptions.dir)

	c, err := fuse.Mount(*mountOptions.dir, fuse.LocalVolume())
	if err != nil {
		glog.Fatal(err)
		return false
	}

	util.OnInterrupt(func() {
		fuse.Unmount(*mountOptions.dir)
		c.Close()
	})

	err = fs.Serve(c, filesys.NewSeaweedFileSystem(*mountOptions.filer))
	if err != nil {
		fuse.Unmount(*mountOptions.dir)
	}

	// check if the mount process has an error to report
	<-c.Ready
	if err := c.MountError; err != nil {
		glog.Fatal(err)
	}

	return true
}
<commit_msg>adjusting for FUSE<commit_after>// +build linux darwin

package command

import (
	"fmt"
	"runtime"

	"bazil.org/fuse"
	"bazil.org/fuse/fs"
	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/util"
	"github.com/chrislusf/seaweedfs/weed/filesys"
)

func runMount(cmd *Command, args []string) bool {
	fmt.Printf("This is SeaweedFS version %s %s %s\n", util.VERSION, runtime.GOOS, runtime.GOARCH)
	if *mountOptions.dir == "" {
		fmt.Printf("Please specify the mount directory via \"-dir\"")
		return false
	}

	fuse.Unmount(*mountOptions.dir)

	c, err := fuse.Mount(
		*mountOptions.dir,
		fuse.VolumeName("SeaweedFS"),
		fuse.FSName("SeaweedFS"),
		fuse.NoAppleDouble(),
		fuse.NoAppleXattr(),
		fuse.ExclCreate(),
		fuse.DaemonTimeout("3600"),
		fuse.AllowOther(),
		fuse.AllowSUID(),
		fuse.DefaultPermissions(),
		// fuse.MaxReadahead(1024*128), // TODO: not tested yet, possibly improving read performance
		fuse.AsyncRead(),
		fuse.WritebackCache(),
	)
	if err != nil {
		glog.Fatal(err)
		return false
	}

	util.OnInterrupt(func() {
		fuse.Unmount(*mountOptions.dir)
		c.Close()
	})

	err = fs.Serve(c, filesys.NewSeaweedFileSystem(*mountOptions.filer))
	if err != nil {
		fuse.Unmount(*mountOptions.dir)
	}

	// check if the mount process has an error to report
	<-c.Ready
	if err := c.MountError; err != nil {
		glog.Fatal(err)
	}

	return true
}
<|endoftext|>


<commit_before>// Copyright 2015 go-swagger maintainers
//
// 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 spec_test

import (
	"encoding/json"
	"path/filepath"
	"strings"
	"testing"

	"github.com/go-openapi/spec"
	"github.com/go-openapi/swag"
	"github.com/stretchr/testify/assert"
)

// mimics what the go-openapi/load does
var yamlLoader = swag.YAMLDoc

func loadOrFail(t *testing.T, path string) *spec.Swagger {
	raw, erl := yamlLoader(path)
	if erl != nil {
		t.Logf("can't load fixture %s: %v", path, erl)
		t.FailNow()
		return nil
	}
	swspec := new(spec.Swagger)
	if err := json.Unmarshal(raw, swspec); err != nil {
		t.FailNow()
		return nil
	}
	return swspec
}

// Test unitary fixture for dev and bug fixing
func Test_Issue1429(t *testing.T) {
	prevPathLoader := spec.PathLoader
	defer func() {
		spec.PathLoader = prevPathLoader
	}()
	spec.PathLoader = yamlLoader
	path := filepath.Join("fixtures", "bugs", "1429", "swagger.yaml")

	// load and full expand
	sp := loadOrFail(t, path)
	err := spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: false})
	if !assert.NoError(t, err) {
		t.FailNow()
		return
	}
	//bbb, _ := json.MarshalIndent(sp, "", " ")
	//t.Log(string(bbb))

	// assert well expanded
	if !assert.Truef(t, (sp.Paths != nil && sp.Paths.Paths != nil), "expected paths to be available in fixture") {
		t.FailNow()
		return
	}
	for _, pi := range sp.Paths.Paths {
		for _, param := range pi.Get.Parameters {
			if assert.NotNilf(t, param.Schema, "expected param schema not to be nil") {
				// all param fixtures are body param with schema
				// all $ref expanded
				assert.Equal(t, "", param.Schema.Ref.String())
			}
		}
		for code, response := range pi.Get.Responses.StatusCodeResponses {
			// all response fixtures are with StatusCodeResponses, but 200
			if code == 200 {
				assert.Nilf(t, response.Schema, "expected response schema to be nil")
				continue
			}
			if assert.NotNilf(t, response.Schema, "expected response schema not to be nil") {
				assert.Equal(t, "", response.Schema.Ref.String())
			}
		}
	}
	for _, def := range sp.Definitions {
		assert.Equal(t, "", def.Ref.String())
	}

	// reload and SkipSchemas: true
	sp = loadOrFail(t, path)
	err = spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: true})
	if !assert.NoError(t, err) {
		t.FailNow()
		return
	}

	// assert well resolved
	if !assert.Truef(t, (sp.Paths != nil && sp.Paths.Paths != nil), "expected paths to be available in fixture") {
		t.FailNow()
		return
	}
	for _, pi := range sp.Paths.Paths {
		for _, param := range pi.Get.Parameters {
			if assert.NotNilf(t, param.Schema, "expected param schema not to be nil") {
				// all param fixtures are body param with schema
				if param.Name == "plainRequest" {
					// this one is expanded
					assert.Equal(t, "", param.Schema.Ref.String())
					continue
				}
				if param.Name == "nestedBody" {
					// this one is local
					assert.True(t, strings.HasPrefix(param.Schema.Ref.String(), "#/definitions/"))
					continue
				}
				if param.Name == "remoteRequest" {
					assert.Contains(t, param.Schema.Ref.String(), "remote/remote.yaml#/")
					continue
				}
				assert.Contains(t, param.Schema.Ref.String(), "responses.yaml#/")
			}
		}
		for code, response := range pi.Get.Responses.StatusCodeResponses {
			// all response fixtures are with StatusCodeResponses, but 200
			if code == 200 {
				assert.Nilf(t, response.Schema, "expected response schema to be nil")
				continue
			}
			if code == 204 {
				assert.Contains(t, response.Schema.Ref.String(), "remote/remote.yaml#/")
				continue
			}
			if code == 404 {
				assert.Equal(t, "", response.Schema.Ref.String())
				continue
			}
			assert.Containsf(t, response.Schema.Ref.String(), "responses.yaml#/", "expected remote ref at resp. %d", code)
		}
	}
	for _, def := range sp.Definitions {
		assert.Contains(t, def.Ref.String(), "responses.yaml#/")
	}
}

func Test_MoreLocalExpansion(t *testing.T) {
	prevPathLoader := spec.PathLoader
	defer func() {
		spec.PathLoader = prevPathLoader
	}()
	spec.PathLoader = yamlLoader
	path := filepath.Join("fixtures", "local_expansion", "spec2.yaml")

	// load and full expand
	sp := loadOrFail(t, path)
	err := spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: false})
	if !assert.NoError(t, err) {
		t.FailNow()
		return
	}
	// asserts all $ref expanded
	jazon, _ := json.MarshalIndent(sp, "", " ")
	assert.NotContains(t, jazon, `"$ref"`)
	//t.Log(string(jazon))
}
<commit_msg>Added fixture for #69<commit_after>// Copyright 2015 go-swagger maintainers
//
// 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 spec_test

import (
	"encoding/json"
	"path/filepath"
	"regexp"
	"strings"
	"testing"

	"github.com/go-openapi/spec"
	"github.com/go-openapi/swag"
	"github.com/stretchr/testify/assert"
)

// mimics what the go-openapi/load does
var yamlLoader = swag.YAMLDoc

func loadOrFail(t *testing.T, path string) *spec.Swagger {
	raw, erl := yamlLoader(path)
	if erl != nil {
		t.Logf("can't load fixture %s: %v", path, erl)
		t.FailNow()
		return nil
	}
	swspec := new(spec.Swagger)
	if err := json.Unmarshal(raw, swspec); err != nil {
		t.FailNow()
		return nil
	}
	return swspec
}

// Test unitary fixture for dev and bug fixing
func Test_Issue1429(t *testing.T) {
	prevPathLoader := spec.PathLoader
	defer func() {
		spec.PathLoader = prevPathLoader
	}()
	spec.PathLoader = yamlLoader
	path := filepath.Join("fixtures", "bugs", "1429", "swagger.yaml")

	// load and full expand
	sp := loadOrFail(t, path)
	err := spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: false})
	if !assert.NoError(t, err) {
		t.FailNow()
		return
	}
	//bbb, _ := json.MarshalIndent(sp, "", " ")
	//t.Log(string(bbb))

	// assert well expanded
	if !assert.Truef(t, (sp.Paths != nil && sp.Paths.Paths != nil), "expected paths to be available in fixture") {
		t.FailNow()
		return
	}
	for _, pi := range sp.Paths.Paths {
		for _, param := range pi.Get.Parameters {
			if assert.NotNilf(t, param.Schema, "expected param schema not to be nil") {
				// all param fixtures are body param with schema
				// all $ref expanded
				assert.Equal(t, "", param.Schema.Ref.String())
			}
		}
		for code, response := range pi.Get.Responses.StatusCodeResponses {
			// all response fixtures are with StatusCodeResponses, but 200
			if code == 200 {
				assert.Nilf(t, response.Schema, "expected response schema to be nil")
				continue
			}
			if assert.NotNilf(t, response.Schema, "expected response schema not to be nil") {
				assert.Equal(t, "", response.Schema.Ref.String())
			}
		}
	}
	for _, def := range sp.Definitions {
		assert.Equal(t, "", def.Ref.String())
	}

	// reload and SkipSchemas: true
	sp = loadOrFail(t, path)
	err = spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: true})
	if !assert.NoError(t, err) {
		t.FailNow()
		return
	}

	// assert well resolved
	if !assert.Truef(t, (sp.Paths != nil && sp.Paths.Paths != nil), "expected paths to be available in fixture") {
		t.FailNow()
		return
	}
	for _, pi := range sp.Paths.Paths {
		for _, param := range pi.Get.Parameters {
			if assert.NotNilf(t, param.Schema, "expected param schema not to be nil") {
				// all param fixtures are body param with schema
				if param.Name == "plainRequest" {
					// this one is expanded
					assert.Equal(t, "", param.Schema.Ref.String())
					continue
				}
				if param.Name == "nestedBody" {
					// this one is local
					assert.True(t, strings.HasPrefix(param.Schema.Ref.String(), "#/definitions/"))
					continue
				}
				if param.Name == "remoteRequest" {
					assert.Contains(t, param.Schema.Ref.String(), "remote/remote.yaml#/")
					continue
				}
				assert.Contains(t, param.Schema.Ref.String(), "responses.yaml#/")
			}
		}
		for code, response := range pi.Get.Responses.StatusCodeResponses {
			// all response fixtures are with StatusCodeResponses, but 200
			if code == 200 {
				assert.Nilf(t, response.Schema, "expected response schema to be nil")
				continue
			}
			if code == 204 {
				assert.Contains(t, response.Schema.Ref.String(), "remote/remote.yaml#/")
				continue
			}
			if code == 404 {
				assert.Equal(t, "", response.Schema.Ref.String())
				continue
			}
			assert.Containsf(t, response.Schema.Ref.String(), "responses.yaml#/", "expected remote ref at resp. %d", code)
		}
	}
	for _, def := range sp.Definitions {
		assert.Contains(t, def.Ref.String(), "responses.yaml#/")
	}
}

func Test_MoreLocalExpansion(t *testing.T) {
	prevPathLoader := spec.PathLoader
	defer func() {
		spec.PathLoader = prevPathLoader
	}()
	spec.PathLoader = yamlLoader
	path := filepath.Join("fixtures", "local_expansion", "spec2.yaml")

	// load and full expand
	sp := loadOrFail(t, path)
	err := spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: false})
	if !assert.NoError(t, err) {
		t.FailNow()
		return
	}
	// asserts all $ref expanded
	jazon, _ := json.MarshalIndent(sp, "", " ")
	assert.NotContains(t, jazon, `"$ref"`)
	//t.Log(string(jazon))
}

func Test_Issue69(t *testing.T) {
	// this checks expansion for the dapperbox spec (circular ref issues)

	path := filepath.Join("fixtures", "bugs", "69", "dapperbox.json")

	// expand with relative path
	// load and expand
	sp := loadOrFail(t, path)
	err := spec.ExpandSpec(sp, &spec.ExpandOptions{RelativeBase: path, SkipSchemas: false})
	if !assert.NoError(t, err) {
		t.FailNow()
		return
	}
	// asserts all $ref expanded
	jazon, _ := json.MarshalIndent(sp, "", " ")

	// assert all $ref maches  "$ref": "#/definitions/something"
	rex := regexp.MustCompile(`"\$ref":\s*"(.+)"`)
	m := rex.FindAllStringSubmatch(string(jazon), -1)
	if assert.NotNil(t, m) {
		for _, matched := range m {
			subMatch := matched[1]
			assert.True(t, strings.HasPrefix(subMatch, "#/definitions/"),
				"expected $ref to be inlined, got: %s", matched[0])
		}
	}
}
<|endoftext|>


<commit_before>package giterminism_test

import (
	"fmt"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/ginkgo/extensions/table"
	. "github.com/onsi/gomega"

	"github.com/werf/werf/integration/pkg/utils"
)

var _ = Describe("config stapel", func() {
	BeforeEach(CommonBeforeEach)

	Context("git.branch", func() {
		type entry struct {
			allowStapelGitBranch bool
			expectedErrSubstring string
		}

		DescribeTable("config.stapel.git.allowBranch",
			func(e entry) {
				fileCreateOrAppend("werf.yaml", `
image: test
from: alpine
git:
- url: https://github.com/werf/werf.git
  branch: test
  to: /app
`)
				gitAddAndCommit("werf.yaml")

				if e.allowStapelGitBranch {
					contentToAppend := `
config:
  stapel:
    git:
      allowBranch: true`
					fileCreateOrAppend("werf-giterminism.yaml", contentToAppend)
					gitAddAndCommit("werf-giterminism.yaml")
				}

				output, err := utils.RunCommand(
					SuiteData.TestDirPath,
					SuiteData.WerfBinPath,
					"config", "render",
				)

				if e.expectedErrSubstring != "" {
					Ω(err).Should(HaveOccurred())
					Ω(string(output)).Should(ContainSubstring(e.expectedErrSubstring))
				} else {
					Ω(err).ShouldNot(HaveOccurred())
				}
			},
			Entry("the remote git branch not allowed", entry{
				expectedErrSubstring: "the configuration with external dependency found in the werf config: git branch directive not allowed",
			}),
			Entry("the remote git branch allowed", entry{
				allowStapelGitBranch: true,
			}),
		)
	})

	Context("mount build_dir", func() {
		type entry struct {
			allowStapelMountBuildDir bool
			expectedErrSubstring     string
		}

		DescribeTable("config.stapel.mount.allowBuildDir",
			func(e entry) {
				fileCreateOrAppend("werf.yaml", `
image: test
from: alpine
mount:
- from: build_dir
  to: /test
`)
				gitAddAndCommit("werf.yaml")

				if e.allowStapelMountBuildDir {
					contentToAppend := `
config:
  stapel:
    mount:
      allowBuildDir: true`
					fileCreateOrAppend("werf-giterminism.yaml", contentToAppend)
					gitAddAndCommit("werf-giterminism.yaml")
				}

				output, err := utils.RunCommand(
					SuiteData.TestDirPath,
					SuiteData.WerfBinPath,
					"config", "render",
				)

				if e.expectedErrSubstring != "" {
					Ω(err).Should(HaveOccurred())
					Ω(string(output)).Should(ContainSubstring(e.expectedErrSubstring))
				} else {
					Ω(err).ShouldNot(HaveOccurred())
				}
			},
			Entry("the build_dir mount not allowed", entry{
				expectedErrSubstring: "the configuration with external dependency found in the werf config: 'mount { from: build_dir, ... }' not allowed",
			}),
			Entry("the build_dir mount allowed", entry{
				allowStapelMountBuildDir: true,
			}),
		)
	})

	Context("mount fromPath", func() {
		type entry struct {
			allowStapelMountFromPathsGlob string
			fromPath                      string
			expectedErrSubstring          string
		}

		DescribeTable("config.stapel.mount.allowFromPaths",
			func(e entry) {
				fileCreateOrAppend("werf.yaml", fmt.Sprintf(`
image: test
from: alpine
mount:
- fromPath: %s
  to: /test
`, e.fromPath))
				gitAddAndCommit("werf.yaml")

				if e.allowStapelMountFromPathsGlob != "" {
					contentToAppend := fmt.Sprintf(`
config:
  stapel:
    mount:
      allowFromPaths: [%s]`, e.allowStapelMountFromPathsGlob)
					fileCreateOrAppend("werf-giterminism.yaml", contentToAppend)
					gitAddAndCommit("werf-giterminism.yaml")
				}

				output, err := utils.RunCommand(
					SuiteData.TestDirPath,
					SuiteData.WerfBinPath,
					"config", "render",
				)

				if e.expectedErrSubstring != "" {
					Ω(err).Should(HaveOccurred())
					Ω(string(output)).Should(ContainSubstring(e.expectedErrSubstring))
				} else {
					Ω(err).ShouldNot(HaveOccurred())
				}
			},
			Entry("the from path /a/b/c not allowed", entry{
				fromPath:             "/a/b/c",
				expectedErrSubstring: "the configuration with external dependency found in the werf config: 'mount { fromPath: /a/b/c, ... }' not allowed",
			}),
			Entry("config.stapel.mount.allowFromPaths (/a/b/c) covers the from path /a/b/c", entry{
				allowStapelMountFromPathsGlob: "/a/b/c",
				fromPath:                      "/a/b/c",
			}),
			Entry("config.stapel.mount.allowFromPaths (/**/*/) covers the from path /a/b/c", entry{
				allowStapelMountFromPathsGlob: "/**/*/",
				fromPath:                      "/a/b/c",
			}),
			Entry("config.stapel.mount.allowFromPaths (/*/) does not cover the from path /a/b/c", entry{
				allowStapelMountFromPathsGlob: "/*/",
				fromPath:                      "/a/b/c",
				expectedErrSubstring:          "the configuration with external dependency found in the werf config: 'mount { fromPath: /a/b/c, ... }' not allowed",
			}),
		)
	})
})
<commit_msg>[tests] Giterminism Suite: config stapel fromLatest tests<commit_after>package giterminism_test

import (
	"fmt"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/ginkgo/extensions/table"
	. "github.com/onsi/gomega"

	"github.com/werf/werf/integration/pkg/utils"
)

var _ = Describe("config stapel", func() {
	BeforeEach(CommonBeforeEach)

	Context("fromLatest", func() {
		type entry struct {
			allowStapelFromLatest bool
			expectedErrSubstring  string
		}

		DescribeTable("config.stapel.allowFromLatest",
			func(e entry) {
				fileCreateOrAppend("werf.yaml", `
fromLatest: true
image: test
from: alpine
`)
				gitAddAndCommit("werf.yaml")

				if e.allowStapelFromLatest {
					contentToAppend := `
config:
  stapel:
    allowFromLatest: true`
					fileCreateOrAppend("werf-giterminism.yaml", contentToAppend)
					gitAddAndCommit("werf-giterminism.yaml")
				}

				output, err := utils.RunCommand(
					SuiteData.TestDirPath,
					SuiteData.WerfBinPath,
					"config", "render",
				)

				if e.expectedErrSubstring != "" {
					Ω(err).Should(HaveOccurred())
					Ω(string(output)).Should(ContainSubstring(e.expectedErrSubstring))
				} else {
					Ω(err).ShouldNot(HaveOccurred())
				}
			},
			Entry("the from latest directive not allowed", entry{
				expectedErrSubstring: "the configuration with external dependency found in the werf config: fromLatest directive not allowed",
			}),
			Entry("the from latest directive allowed", entry{
				allowStapelFromLatest: true,
			}),
		)
	})

	Context("git.branch", func() {
		type entry struct {
			allowStapelGitBranch bool
			expectedErrSubstring string
		}

		DescribeTable("config.stapel.git.allowBranch",
			func(e entry) {
				fileCreateOrAppend("werf.yaml", `
image: test
from: alpine
git:
- url: https://github.com/werf/werf.git
  branch: test
  to: /app
`)
				gitAddAndCommit("werf.yaml")

				if e.allowStapelGitBranch {
					contentToAppend := `
config:
  stapel:
    git:
      allowBranch: true`
					fileCreateOrAppend("werf-giterminism.yaml", contentToAppend)
					gitAddAndCommit("werf-giterminism.yaml")
				}

				output, err := utils.RunCommand(
					SuiteData.TestDirPath,
					SuiteData.WerfBinPath,
					"config", "render",
				)

				if e.expectedErrSubstring != "" {
					Ω(err).Should(HaveOccurred())
					Ω(string(output)).Should(ContainSubstring(e.expectedErrSubstring))
				} else {
					Ω(err).ShouldNot(HaveOccurred())
				}
			},
			Entry("the remote git branch not allowed", entry{
				expectedErrSubstring: "the configuration with external dependency found in the werf config: git branch directive not allowed",
			}),
			Entry("the remote git branch allowed", entry{
				allowStapelGitBranch: true,
			}),
		)
	})

	Context("mount build_dir", func() {
		type entry struct {
			allowStapelMountBuildDir bool
			expectedErrSubstring     string
		}

		DescribeTable("config.stapel.mount.allowBuildDir",
			func(e entry) {
				fileCreateOrAppend("werf.yaml", `
image: test
from: alpine
mount:
- from: build_dir
  to: /test
`)
				gitAddAndCommit("werf.yaml")

				if e.allowStapelMountBuildDir {
					contentToAppend := `
config:
  stapel:
    mount:
      allowBuildDir: true`
					fileCreateOrAppend("werf-giterminism.yaml", contentToAppend)
					gitAddAndCommit("werf-giterminism.yaml")
				}

				output, err := utils.RunCommand(
					SuiteData.TestDirPath,
					SuiteData.WerfBinPath,
					"config", "render",
				)

				if e.expectedErrSubstring != "" {
					Ω(err).Should(HaveOccurred())
					Ω(string(output)).Should(ContainSubstring(e.expectedErrSubstring))
				} else {
					Ω(err).ShouldNot(HaveOccurred())
				}
			},
			Entry("the build_dir mount not allowed", entry{
				expectedErrSubstring: "the configuration with external dependency found in the werf config: 'mount { from: build_dir, ... }' not allowed",
			}),
			Entry("the build_dir mount allowed", entry{
				allowStapelMountBuildDir: true,
			}),
		)
	})

	Context("mount fromPath", func() {
		type entry struct {
			allowStapelMountFromPathsGlob string
			fromPath                      string
			expectedErrSubstring          string
		}

		DescribeTable("config.stapel.mount.allowFromPaths",
			func(e entry) {
				fileCreateOrAppend("werf.yaml", fmt.Sprintf(`
image: test
from: alpine
mount:
- fromPath: %s
  to: /test
`, e.fromPath))
				gitAddAndCommit("werf.yaml")

				if e.allowStapelMountFromPathsGlob != "" {
					contentToAppend := fmt.Sprintf(`
config:
  stapel:
    mount:
      allowFromPaths: [%s]`, e.allowStapelMountFromPathsGlob)
					fileCreateOrAppend("werf-giterminism.yaml", contentToAppend)
					gitAddAndCommit("werf-giterminism.yaml")
				}

				output, err := utils.RunCommand(
					SuiteData.TestDirPath,
					SuiteData.WerfBinPath,
					"config", "render",
				)

				if e.expectedErrSubstring != "" {
					Ω(err).Should(HaveOccurred())
					Ω(string(output)).Should(ContainSubstring(e.expectedErrSubstring))
				} else {
					Ω(err).ShouldNot(HaveOccurred())
				}
			},
			Entry("the from path /a/b/c not allowed", entry{
				fromPath:             "/a/b/c",
				expectedErrSubstring: "the configuration with external dependency found in the werf config: 'mount { fromPath: /a/b/c, ... }' not allowed",
			}),
			Entry("config.stapel.mount.allowFromPaths (/a/b/c) covers the from path /a/b/c", entry{
				allowStapelMountFromPathsGlob: "/a/b/c",
				fromPath:                      "/a/b/c",
			}),
			Entry("config.stapel.mount.allowFromPaths (/**/*/) covers the from path /a/b/c", entry{
				allowStapelMountFromPathsGlob: "/**/*/",
				fromPath:                      "/a/b/c",
			}),
			Entry("config.stapel.mount.allowFromPaths (/*/) does not cover the from path /a/b/c", entry{
				allowStapelMountFromPathsGlob: "/*/",
				fromPath:                      "/a/b/c",
				expectedErrSubstring:          "the configuration with external dependency found in the werf config: 'mount { fromPath: /a/b/c, ... }' not allowed",
			}),
		)
	})
})
<|endoftext|>


<commit_before>// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package resumer

import (
	"fmt"
	"time"

	"launchpad.net/tomb"

	"launchpad.net/juju-core/log"
)

// defaultInterval is the standard value for the interval setting.
const defaultInterval = time.Minute

// interval sets how often the resuming is called.
var interval = defaultInterval

// TransactionResumer defines the interface for types capable to
// resume transactions.
type TransactionResumer interface {
	// ResumeTransactions resumes all pending transactions.
	ResumeTransactions() error
}

// Resumer is responsible for a periodical resuming of pending transactions.
type Resumer struct {
	tomb tomb.Tomb
	tr   TransactionResumer
}

// NewResumer periodically resumes pending transactions.
func NewResumer(tr TransactionResumer) *Resumer {
	rr := &Resumer{tr: tr}
	go func() {
		defer rr.tomb.Done()
		rr.tomb.Kill(rr.loop())
	}()
	return rr
}

func (rr *Resumer) String() string {
	return fmt.Sprintf("resumer")
}

func (rr *Resumer) Kill() {
	rr.tomb.Kill(nil)
}

func (rr *Resumer) Stop() error {
	rr.tomb.Kill(nil)
	return rr.tomb.Wait()
}

func (rr *Resumer) Wait() error {
	return rr.tomb.Wait()
}

func (rr *Resumer) loop() error {
	for {
		select {
		case <-rr.tomb.Dying():
			return tomb.ErrDying
		case <-time.After(interval):
			if err := rr.tr.ResumeTransactions(); err != nil {
				log.Errorf("worker/resumer: cannot resume transactions: %v", err)
			}
		}
	}
}
<commit_msg>update resumer<commit_after>// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package resumer

import (
	"fmt"
	"time"

	"github.com/juju/loggo"
	"launchpad.net/tomb"
)

var logger = loggo.GetLogger("juju.worker.resumer")

// defaultInterval is the standard value for the interval setting.
const defaultInterval = time.Minute

// interval sets how often the resuming is called.
var interval = defaultInterval

// TransactionResumer defines the interface for types capable to
// resume transactions.
type TransactionResumer interface {
	// ResumeTransactions resumes all pending transactions.
	ResumeTransactions() error
}

// Resumer is responsible for a periodical resuming of pending transactions.
type Resumer struct {
	tomb tomb.Tomb
	tr   TransactionResumer
}

// NewResumer periodically resumes pending transactions.
func NewResumer(tr TransactionResumer) *Resumer {
	rr := &Resumer{tr: tr}
	go func() {
		defer rr.tomb.Done()
		rr.tomb.Kill(rr.loop())
	}()
	return rr
}

func (rr *Resumer) String() string {
	return fmt.Sprintf("resumer")
}

func (rr *Resumer) Kill() {
	rr.tomb.Kill(nil)
}

func (rr *Resumer) Stop() error {
	rr.tomb.Kill(nil)
	return rr.tomb.Wait()
}

func (rr *Resumer) Wait() error {
	return rr.tomb.Wait()
}

func (rr *Resumer) loop() error {
	for {
		select {
		case <-rr.tomb.Dying():
			return tomb.ErrDying
		case <-time.After(interval):
			if err := rr.tr.ResumeTransactions(); err != nil {
				logger.Errorf("cannot resume transactions: %v", err)
			}
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"os"
	"strconv"
	"ember/cli"
)

func main() {
	args := os.Args[1:]
	if len(args) == 0 {
		cli.Errln("usage: [-host=] [-port=] cmd(eg: help)")
		os.Exit(1)
	}

	host, args := cli.PopArg("host", "127.0.0.1", args)
	port, args := cli.PopArg("port", "8080", args)

	server := &CliServer{port}
	client, err := NewClient("http://" + host + ":" + port)
	cli.Check(err)

	cmds := cli.NewCmds()
	cmds.Reg("run", "run server", server.CmdRun)
	cmds.Reg("stop", "stop server", client.CmdStop)
	cmds.Reg("api", "call server api by: name [arg] [arg]...", client.CmdCall)

	cmds.Run(args)
}

type CliServer struct {
	port string
}

func (p *CliServer) CmdRun(args []string) {
	cli.ParseFlag(flag.NewFlagSet("run", flag.ContinueOnError), args)
	n, err := strconv.Atoi(p.port)
	cli.Check(err)
	err = Launch(n)
	cli.Check(err)
}

func (p *Client) CmdCall(args []string) {
	ret, err := p.Rpc.Call(args)
	cli.Check(err)
	for i := 0; i < len(ret) - 1; i++ {
		fmt.Printf("%#v", ret[i])
		if i + 1 != len(ret) - 1 {
			fmt.Printf(", ")
		}
	}
	fmt.Printf("\n")
}

func (p *Client) CmdStop(args []string) {
	cli.ParseFlag(flag.NewFlagSet("stop", flag.ContinueOnError), args)
	cli.Check(p.Stop())
}
<commit_msg>Ajusted: cli api output<commit_after>package main

import (
	"flag"
	"fmt"
	"os"
	"strconv"
	"ember/cli"
)

func main() {
	args := os.Args[1:]
	if len(args) == 0 {
		cli.Errln("usage: [-host=] [-port=] cmd(eg: help)")
		os.Exit(1)
	}

	host, args := cli.PopArg("host", "127.0.0.1", args)
	port, args := cli.PopArg("port", "8080", args)

	server := &CliServer{port}
	client, err := NewClient("http://" + host + ":" + port)
	cli.Check(err)

	cmds := cli.NewCmds()
	cmds.Reg("run", "run server", server.CmdRun)
	cmds.Reg("stop", "stop server", client.CmdStop)
	cmds.Reg("api", "call server api by: name [arg] [arg]...", client.CmdCall)

	cmds.Run(args)
}

type CliServer struct {
	port string
}

func (p *CliServer) CmdRun(args []string) {
	cli.ParseFlag(flag.NewFlagSet("run", flag.ContinueOnError), args)
	n, err := strconv.Atoi(p.port)
	cli.Check(err)
	err = Launch(n)
	cli.Check(err)
}

func (p *Client) CmdCall(args []string) {
	ret, err := p.Rpc.Call(args)
	cli.Check(err)
	for i := 0; i < len(ret) - 1; i++ {
		val := fmt.Sprintf("%#v", ret[i])
		if val[0] == '"' && val[len(val) - 1] =='"' && len(val) > 2 {
			val = val[1:len(val) - 1]
		}
		fmt.Print(val)
		if i + 1 != len(ret) - 1 {
			fmt.Printf(", ")
		}
	}
	fmt.Printf("\n")
}

func (p *Client) CmdStop(args []string) {
	p.CmdCall([]string{"Stop"})
}
<|endoftext|>


<commit_before>package Writer

import (
	. "Common"
	. "Layout"
	. "Marshal"
	. "SchemaHandler"
	"encoding/binary"
	"git.apache.org/thrift.git/lib/go/thrift"
	"log"
	"os"
	"parquet"
	"reflect"
)

func WriteParquet(file *os.File, srcInterface interface{}, schemaHandler *SchemaHandler, np int) {
	var pageSize int64 = 8 * 1024              //8K
	var rowGroupSize int64 = 256 * 1024 * 1024 //256MB

	src := reflect.ValueOf(srcInterface)
	ln := src.Len()

	footer := parquet.NewFileMetaData()
	footer.Version = 1
	footer.Schema = append(footer.Schema, schemaHandler.SchemaElements...)

	file.Write([]byte("PAR1"))
	var offset int64 = 4

	i := 0
	for i < ln {
		j := i
		var size int64 = 0
		for j < ln && size < rowGroupSize {
			size += SizeOf(src.Index(j))
			j++
		}

		tableMapList := make([]*map[string]*Table, np)
		doneChan := make(chan int)
		delta := (j - i) / np
		for c := 0; c < np; c++ {
			bgn := i + c*delta
			end := bgn + delta
			if c == np-1 {
				end = j
			}
			go func(index int) {
				tableMapList[index] = Marshal(srcInterface, bgn, end, schemaHandler)
				doneChan <- 0
			}(c)
		}

		for c := 0; c < np; c++ {
			<-doneChan
		}

		//table -> pages
		pagesMap := make(map[string][]*Page)
		for _, tableMap := range tableMapList {
			for name := range *tableMap {
				pagesMap[name] = make([]*Page, 0)
			}
		}
		nameList := make([]string, len(pagesMap))
		k := 0
		for name := range pagesMap {
			nameList[k] = name
			k++
		}

		delta = (len(nameList)) / np
		for c := 0; c < np; c++ {
			bgn := c * delta
			end := bgn + delta
			if c == np-1 {
				end = len(nameList)
			}

			go func(names []string) {
				for _, name := range names {
					for _, tableMap := range tableMapList {
						tmp, _ := TableToDataPages((*tableMap)[name], int32(pageSize), parquet.CompressionCodec_SNAPPY)
						pagesMap[name] = append(pagesMap[name], tmp...)
					}
				}
				doneChan <- 0
			}(nameList[bgn:end])
		}

		for c := 0; c < np; c++ {
			<-doneChan
		}

		//pages -> chunk
		chunkMap := make(map[string]*Chunk)
		for name, pages := range pagesMap {
			chunkMap[name] = PagesToChunk(pages)
		}

		//chunks -> rowGroup
		rowGroup := NewRowGroup()
		rowGroup.RowGroupHeader.Columns = make([]*parquet.ColumnChunk, 0)
		for _, chunk := range chunkMap {
			rowGroup.Chunks = append(rowGroup.Chunks, chunk)
			rowGroup.RowGroupHeader.TotalByteSize += chunk.ChunkHeader.MetaData.TotalCompressedSize
			rowGroup.RowGroupHeader.Columns = append(rowGroup.RowGroupHeader.Columns, chunk.ChunkHeader)
		}
		rowGroup.RowGroupHeader.NumRows = int64(j - i)

		for k := 0; k < len(rowGroup.Chunks); k++ {
			rowGroup.Chunks[k].ChunkHeader.MetaData.DataPageOffset = offset
			rowGroup.Chunks[k].ChunkHeader.FileOffset = offset

			for l := 0; l < len(rowGroup.Chunks[k].Pages); l++ {
				//data := rowGroup.Chunks[k].Pages[l].DataPageCompress(parquet.CompressionCodec_SNAPPY)
				data := rowGroup.Chunks[k].Pages[l].RawData
				file.Write(data)
				offset += int64(len(data))
			}
		}
		footer.NumRows += int64(j - i)
		footer.RowGroups = append(footer.RowGroups, rowGroup.RowGroupHeader)

		i = j
	}

	ts := thrift.NewTSerializer()
	ts.Protocol = thrift.NewTCompactProtocolFactory().GetProtocol(ts.Transport)
	footerBuf, _ := ts.Write(footer)

	file.Write(footerBuf)
	footerSizeBuf := make([]byte, 4)
	binary.LittleEndian.PutUint32(footerSizeBuf, uint32(len(footerBuf)))
	file.Write(footerSizeBuf)
	file.Write([]byte("PAR1"))

	log.Println(footer)

}
<commit_msg>add mutex in write<commit_after>package Writer

import (
	. "Common"
	. "Layout"
	. "Marshal"
	. "SchemaHandler"
	"encoding/binary"
	"git.apache.org/thrift.git/lib/go/thrift"
	"log"
	"os"
	"parquet"
	"reflect"
	"sync"
)

func WriteParquet(file *os.File, srcInterface interface{}, schemaHandler *SchemaHandler, np int) {
	var pageSize int64 = 8 * 1024              //8K
	var rowGroupSize int64 = 256 * 1024 * 1024 //256MB

	src := reflect.ValueOf(srcInterface)
	ln := src.Len()

	footer := parquet.NewFileMetaData()
	footer.Version = 1
	footer.Schema = append(footer.Schema, schemaHandler.SchemaElements...)

	file.Write([]byte("PAR1"))
	var offset int64 = 4

	i := 0
	for i < ln {
		j := i
		var size int64 = 0
		for j < ln && size < rowGroupSize {
			size += SizeOf(src.Index(j))
			j++
		}

		tableMapList := make([]*map[string]*Table, np)
		doneChan := make(chan int)
		delta := (j - i + np - 1) / np
		for c := 0; c < np; c++ {
			bgn := i + c*delta
			end := bgn + delta
			if end > j {
				end = j
			}
			if bgn >= j {
				bgn, end = i, i
			}

			go func(index int) {
				tableMapList[index] = Marshal(srcInterface, bgn, end, schemaHandler)
				doneChan <- 0
			}(c)
		}

		for c := 0; c < np; c++ {
			<-doneChan
		}

		//table -> pages
		var mutex = &sync.Mutex{}
		pagesMap := make(map[string][]*Page)
		for _, tableMap := range tableMapList {
			for name := range *tableMap {
				pagesMap[name] = make([]*Page, 0)
			}
		}
		nameList := make([]string, len(pagesMap))
		k := 0
		for name := range pagesMap {
			nameList[k] = name
			k++
		}

		delta = (len(nameList) + np - 1) / np
		for c := 0; c < np; c++ {
			bgn := c * delta
			end := bgn + delta
			if end > len(nameList) {
				end = len(nameList)
			}
			if bgn >= len(nameList) {
				bgn, end = 0, 0
			}

			go func(names []string) {
				for _, name := range names {
					for _, tableMap := range tableMapList {
						tmp, _ := TableToDataPages((*tableMap)[name], int32(pageSize), parquet.CompressionCodec_SNAPPY)
						mutex.Lock()
						pagesMap[name] = append(pagesMap[name], tmp...)
						mutex.Unlock()
					}
				}
				doneChan <- 0
			}(nameList[bgn:end])
		}

		for c := 0; c < np; c++ {
			<-doneChan
		}

		//pages -> chunk
		chunkMap := make(map[string]*Chunk)
		for name, pages := range pagesMap {
			chunkMap[name] = PagesToChunk(pages)
		}

		//chunks -> rowGroup
		rowGroup := NewRowGroup()
		rowGroup.RowGroupHeader.Columns = make([]*parquet.ColumnChunk, 0)
		for _, chunk := range chunkMap {
			rowGroup.Chunks = append(rowGroup.Chunks, chunk)
			rowGroup.RowGroupHeader.TotalByteSize += chunk.ChunkHeader.MetaData.TotalCompressedSize
			rowGroup.RowGroupHeader.Columns = append(rowGroup.RowGroupHeader.Columns, chunk.ChunkHeader)
		}
		rowGroup.RowGroupHeader.NumRows = int64(j - i)

		for k := 0; k < len(rowGroup.Chunks); k++ {
			rowGroup.Chunks[k].ChunkHeader.MetaData.DataPageOffset = offset
			rowGroup.Chunks[k].ChunkHeader.FileOffset = offset

			for l := 0; l < len(rowGroup.Chunks[k].Pages); l++ {
				//data := rowGroup.Chunks[k].Pages[l].DataPageCompress(parquet.CompressionCodec_SNAPPY)
				data := rowGroup.Chunks[k].Pages[l].RawData
				file.Write(data)
				offset += int64(len(data))
			}
		}
		footer.NumRows += int64(j - i)
		footer.RowGroups = append(footer.RowGroups, rowGroup.RowGroupHeader)

		i = j
	}

	ts := thrift.NewTSerializer()
	ts.Protocol = thrift.NewTCompactProtocolFactory().GetProtocol(ts.Transport)
	footerBuf, _ := ts.Write(footer)

	file.Write(footerBuf)
	footerSizeBuf := make([]byte, 4)
	binary.LittleEndian.PutUint32(footerSizeBuf, uint32(len(footerBuf)))
	file.Write(footerSizeBuf)
	file.Write([]byte("PAR1"))

	log.Println(footer)

}
<|endoftext|>


<commit_before>package manager

import (
	"chatroom/models"
	"strconv"
)

// SetRoomSilence 设置某个房间的全员禁言状态
func SetRoomSilence(room int, status string) bool {
	redisConn := models.RedisConnPool.Get()
	_, err := redisConn.Do("SET", "RoomConfig:"+strconv.Itoa(room)+":Silence", status)
	redisConn.Close()
	if err != nil {
		return false
	}
	return true
}

//SpeakNotAllowed 禁言某个房间的某个人
func SpeakNotAllowed(room int, uid int, status string) bool {
	redisConn := models.RedisConnPool.Get()
	_, err := redisConn.Do("SET", "RoomConfig:"+strconv.Itoa(room)+":SpeakNotAllowed:"+strconv.Itoa(uid), status)
	redisConn.Close()
	if err != nil {
		return false
	}
	return true
}
<commit_msg>设置用户禁言调整<commit_after>package manager

import (
	"chatroom/models"
	"strconv"
)

// SetRoomSilence 设置某个房间的全员禁言状态
func SetRoomSilence(room int, status string) bool {
	redisConn := models.RedisConnPool.Get()
	_, err := redisConn.Do("SET", "RoomConfig:"+strconv.Itoa(room)+":Silence", status)
	redisConn.Close()
	if err != nil {
		return false
	}
	return true
}

//SpeakNotAllowed 禁言某个房间的某个人
func SpeakNotAllowed(room int, uid int, status string) bool {
	redisConn := models.RedisConnPool.Get()
	var err error
	if status == "true" {
		_, err = redisConn.Do("SET", "RoomConfig:"+strconv.Itoa(room)+":SpeakNotAllowed:"+strconv.Itoa(uid), status)
	} else {
		_, err = redisConn.Do("DEL", "RoomConfig:"+strconv.Itoa(room)+":SpeakNotAllowed:"+strconv.Itoa(uid), status)
	}
	redisConn.Close()
	if err != nil {
		return false
	}
	return true
}
<|endoftext|>


<commit_before>package main

import (
	// "fmt" // tests
	// "errors"
	// "github.com/mki1967/go-mki3d/mki3d"
	"github.com/go-gl/gl/v3.3-core/gl"
	"github.com/go-gl/mathgl/mgl32"
	"github.com/mki1967/test-go-mki3d/tmki3d"
	// "github.com/go-gl/glfw/v3.2/glfw"
	// "math/rand"
)

const MARGIN = 40 // margin for bounding box of the stage

// data structure for the game
type Mki3dGame struct {
	// assets info
	AssetsPtr *Assets
	// GL shaders
	ShaderPtr *tmki3d.Shader
	// Shape data shaders
	StageDSPtr   *tmki3d.DataShader
	FrameDSPtr   *tmki3d.DataShader // frame of the bounding box (computed for the stage)
	SectorsDSPtr *tmki3d.DataShader
	TokenDSPtr   *tmki3d.DataShader
	MonsterDSPtr *tmki3d.DataShader

	VMin, VMax mgl32.Vec3 // corners of the bounding box of the stage (computed with the MARGIN)

	TravelerPtr *Traveler // the first person (the player)

}

// Make game structure with the shader and without any data.
// Prepare assets info using pathToAssets.
// Return pointer to the strucure.
func MakeEmptyGame(pathToAssets string) (*Mki3dGame, error) {
	var game Mki3dGame

	shaderPtr, err := tmki3d.MakeShader()
	if err != nil {
		return nil, err
	}

	game.ShaderPtr = shaderPtr

	assetsPtr, err := LoadAssets(pathToAssets)
	if err != nil {
		return nil, err
	}
	game.AssetsPtr = assetsPtr
	return &game, nil
}

// Load sectors shape and init the SectorsDSPtr.
func (game *Mki3dGame) InitSectors() error {

	sectorsPtr, err := game.AssetsPtr.LoadRandomSectors()
	if err != nil {
		return err
	}

	sectorsDataShaderPtr, err := tmki3d.MakeDataShader(game.ShaderPtr, sectorsPtr)
	if err != nil {
		return err
	}

	sectorsDataShaderPtr.UniPtr.SetSimple()

	if game.SectorsDSPtr != nil {
		game.SectorsDSPtr.DeleteData() // free old GL buffers
	}

	game.SectorsDSPtr = sectorsDataShaderPtr

	return nil
}

// Load token shape and init the tokenDSPtr.
func (game *Mki3dGame) InitToken() error {

	tokenPtr, err := game.AssetsPtr.LoadRandomToken()
	if err != nil {
		return err
	}

	tokenDataShaderPtr, err := tmki3d.MakeDataShader(game.ShaderPtr, tokenPtr)
	if err != nil {
		return err
	}

	tokenDataShaderPtr.UniPtr.SetSimple()

	if game.TokenDSPtr != nil {
		game.TokenDSPtr.DeleteData() // free old GL buffers
	}

	game.TokenDSPtr = tokenDataShaderPtr

	return nil
}

// Load token shape and init the tokenDSPtr.
func (game *Mki3dGame) InitMonster() error {

	monsterPtr, err := game.AssetsPtr.LoadRandomMonster()
	if err != nil {
		return err
	}

	monsterDataShaderPtr, err := tmki3d.MakeDataShader(game.ShaderPtr, monsterPtr)
	if err != nil {
		return err
	}

	monsterDataShaderPtr.UniPtr.SetSimple()

	if game.MonsterDSPtr != nil {
		game.MonsterDSPtr.DeleteData() // free old GL buffers
	}

	game.MonsterDSPtr = monsterDataShaderPtr

	return nil
}

// Load stage shape and init the related data.
func (game *Mki3dGame) InitStage(width, height int) error {

	stagePtr, err := game.AssetsPtr.LoadRandomStage()
	if err != nil {
		return err
	}

	stageDataShaderPtr, err := tmki3d.MakeDataShader(game.ShaderPtr, stagePtr)
	if err != nil {
		return err
	}

	stageDataShaderPtr.UniPtr.SetSimple()
	stageDataShaderPtr.UniPtr.SetProjectionFromMki3d(stagePtr, width, height)
	stageDataShaderPtr.UniPtr.SetLightFromMki3d(stagePtr)

	stageDataShaderPtr.UniPtr.ViewUni = mgl32.Ident4()
	stageDataShaderPtr.UniPtr.ViewUni.SetCol(3, mgl32.Vec3(stageDataShaderPtr.Mki3dPtr.Cursor.Position).Mul(-1).Vec4(1))

	if game.StageDSPtr != nil {
		game.StageDSPtr.DeleteData() // free old GL buffers
	}

	game.StageDSPtr = stageDataShaderPtr

	// compute bounding box of the stage: VMin, VMax

	game.VMax = mgl32.Vec3(stagePtr.Cursor.Position) // cursror position should be included - the starting poin of traveler
	game.VMin = game.VMax

	for _, seg := range stagePtr.Model.Segments {
		for _, point := range seg {
			for d := range point.Position {
				if game.VMax[d] < point.Position[d] {
					game.VMax[d] = point.Position[d]
				}
				if game.VMin[d] > point.Position[d] {
					game.VMin[d] = point.Position[d]
				}
			}

		}
	}

	for _, tr := range stagePtr.Model.Triangles {
		for _, point := range tr {
			for d := range point.Position {
				if game.VMax[d] < point.Position[d] {
					game.VMax[d] = point.Position[d]
				}
				if game.VMin[d] > point.Position[d] {
					game.VMin[d] = point.Position[d]
				}
			}

		}
	}

	// fmt.Println(game.VMin, game.VMax) // test

	return nil
}

// Redraw the game stage
func (game *Mki3dGame) Redraw() {
	gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) // to be moved to redraw ?
	// draw stage
	game.StageDSPtr.SetBackgroundColor()
	game.StageDSPtr.DrawStage()
	// draw tokens
	game.TokenDSPtr.DrawModel()
	// draw monsters
	game.MonsterDSPtr.DrawModel()

	// draw sectors
	gl.Disable(gl.DEPTH_TEST)
	game.SectorsDSPtr.DrawStage()
	gl.Enable(gl.DEPTH_TEST)

}

type Traveler struct {
	Position mgl32.Vec3 // position
	/* orientation */
	rotXZ float32 // horizontal rotation (in degrees)
	rotYZ float32 // vertical rotation (in degrees)

}
<commit_msg>frame of the bounding box ...<commit_after>package main

import (
	"fmt" // tests
	// "errors"
	"github.com/go-gl/gl/v3.3-core/gl"
	"github.com/go-gl/mathgl/mgl32"
	"github.com/mki1967/go-mki3d/mki3d"
	"github.com/mki1967/test-go-mki3d/tmki3d"
	// "github.com/go-gl/glfw/v3.2/glfw"
	// "math/rand"
)

const BoxMargin = 40 // margin for bounding box of the stage

var FrameColor = mki3d.Vector3dType{1.0, 1.0, 1.0} // color of the bounding box frame

// data structure for the game
type Mki3dGame struct {
	// assets info
	AssetsPtr *Assets
	// GL shaders
	ShaderPtr *tmki3d.Shader
	// Shape data shaders
	StageDSPtr   *tmki3d.DataShader
	FrameDSPtr   *tmki3d.DataShader // frame of the bounding box (computed for the stage)
	SectorsDSPtr *tmki3d.DataShader
	TokenDSPtr   *tmki3d.DataShader
	MonsterDSPtr *tmki3d.DataShader

	VMin, VMax mgl32.Vec3 // corners of the bounding box of the stage (computed with the BoxMargin)

	TravelerPtr *Traveler // the first person (the player)

}

// Make game structure with the shader and without any data.
// Prepare assets info using pathToAssets.
// Return pointer to the strucure.
func MakeEmptyGame(pathToAssets string) (*Mki3dGame, error) {
	var game Mki3dGame

	shaderPtr, err := tmki3d.MakeShader()
	if err != nil {
		return nil, err
	}

	game.ShaderPtr = shaderPtr

	assetsPtr, err := LoadAssets(pathToAssets)
	if err != nil {
		return nil, err
	}
	game.AssetsPtr = assetsPtr
	return &game, nil
}

// Load sectors shape and init the SectorsDSPtr.
func (game *Mki3dGame) InitSectors() error {

	sectorsPtr, err := game.AssetsPtr.LoadRandomSectors()
	if err != nil {
		return err
	}

	sectorsDataShaderPtr, err := tmki3d.MakeDataShader(game.ShaderPtr, sectorsPtr)
	if err != nil {
		return err
	}

	sectorsDataShaderPtr.UniPtr.SetSimple()

	if game.SectorsDSPtr != nil {
		game.SectorsDSPtr.DeleteData() // free old GL buffers
	}

	game.SectorsDSPtr = sectorsDataShaderPtr

	return nil
}

// Load token shape and init the tokenDSPtr.
func (game *Mki3dGame) InitToken() error {

	tokenPtr, err := game.AssetsPtr.LoadRandomToken()
	if err != nil {
		return err
	}

	tokenDataShaderPtr, err := tmki3d.MakeDataShader(game.ShaderPtr, tokenPtr)
	if err != nil {
		return err
	}

	tokenDataShaderPtr.UniPtr.SetSimple()

	if game.TokenDSPtr != nil {
		game.TokenDSPtr.DeleteData() // free old GL buffers
	}

	game.TokenDSPtr = tokenDataShaderPtr

	return nil
}

// Load token shape and init the tokenDSPtr.
func (game *Mki3dGame) InitMonster() error {

	monsterPtr, err := game.AssetsPtr.LoadRandomMonster()
	if err != nil {
		return err
	}

	monsterDataShaderPtr, err := tmki3d.MakeDataShader(game.ShaderPtr, monsterPtr)
	if err != nil {
		return err
	}

	monsterDataShaderPtr.UniPtr.SetSimple()

	if game.MonsterDSPtr != nil {
		game.MonsterDSPtr.DeleteData() // free old GL buffers
	}

	game.MonsterDSPtr = monsterDataShaderPtr

	return nil
}

// Load stage shape and init the related data.
func (game *Mki3dGame) InitStage(width, height int) error {

	stagePtr, err := game.AssetsPtr.LoadRandomStage()
	if err != nil {
		return err
	}

	stageDataShaderPtr, err := tmki3d.MakeDataShader(game.ShaderPtr, stagePtr)
	if err != nil {
		return err
	}

	stageDataShaderPtr.UniPtr.SetSimple()
	stageDataShaderPtr.UniPtr.SetProjectionFromMki3d(stagePtr, width, height)
	stageDataShaderPtr.UniPtr.SetLightFromMki3d(stagePtr)

	stageDataShaderPtr.UniPtr.ViewUni = mgl32.Ident4()
	stageDataShaderPtr.UniPtr.ViewUni.SetCol(3, mgl32.Vec3(stageDataShaderPtr.Mki3dPtr.Cursor.Position).Mul(-1).Vec4(1))

	if game.StageDSPtr != nil {
		game.StageDSPtr.DeleteData() // free old GL buffers
	}

	game.StageDSPtr = stageDataShaderPtr

	game.copmuteVMinVMax() // compute bounding box of the stage: VMin, VMax
	game.copmuteFrame()    // visible line frame of the bounding box

	return nil
}

// recompute bounding box  with the BoxMargin  corners of the stage.
func (game *Mki3dGame) copmuteVMinVMax() {
	stagePtr := game.StageDSPtr.Mki3dPtr
	game.VMax = mgl32.Vec3(stagePtr.Cursor.Position) // cursror position should be included - the starting poin of traveler
	game.VMin = game.VMax

	for _, seg := range stagePtr.Model.Segments {
		for _, point := range seg {
			for d := range point.Position {
				if game.VMax[d] < point.Position[d] {
					game.VMax[d] = point.Position[d]
				}
				if game.VMin[d] > point.Position[d] {
					game.VMin[d] = point.Position[d]
				}
			}

		}
	}

	for _, tr := range stagePtr.Model.Triangles {
		for _, point := range tr {
			for d := range point.Position {
				if game.VMax[d] < point.Position[d] {
					game.VMax[d] = point.Position[d]
				}
				if game.VMin[d] > point.Position[d] {
					game.VMin[d] = point.Position[d]
				}
			}

		}
	}

	m := mgl32.Vec3{BoxMargin, BoxMargin, BoxMargin}

	game.VMin = game.VMin.Sub(m)
	game.VMax = game.VMax.Add(m)
	fmt.Println(game.VMin, game.VMax) // test
}

// recompute frame of the bounding box corners of the stage.
func (game *Mki3dGame) copmuteFrame() {
	a := game.VMin
	b := game.VMax

	v000 := mki3d.Vector3dType(a)
	v001 := mki3d.Vector3dType{a[0], a[1], b[2]}
	v010 := mki3d.Vector3dType{a[0], b[1], a[2]}
	v011 := mki3d.Vector3dType{a[0], b[1], b[2]}
	v100 := mki3d.Vector3dType{b[0], a[1], a[2]}
	v101 := mki3d.Vector3dType{b[0], a[1], b[2]}
	v110 := mki3d.Vector3dType{b[0], b[1], a[2]}
	v111 := mki3d.Vector3dType(b)

	lines := [][2]mki3d.Vector3dType{
		{v000, v001},
		{v010, v011},
		{v100, v101},
		{v110, v111},

		{v000, v010},
		{v001, v011},
		{v100, v110},
		{v101, v111},

		{v000, v100},
		{v001, v101},
		{v010, v110},
		{v011, v111}}

	segments := mki3d.SegmentsType(make([]mki3d.SegmentType, 12))

	for i := range segments {
		segments[i] = mki3d.SegmentType{
			{Position: lines[i][0], Color: FrameColor},
			{Position: lines[i][1], Color: FrameColor}}
	}

	var frameMki3d mki3d.Mki3dType

	frameMki3d.Model.Segments = segments

	dsPtr, err := tmki3d.MakeDataShader(game.ShaderPtr, &frameMki3d)

	if err != nil {
		panic(err)
	}

	dsPtr.UniPtr.SetSimple()

	if game.FrameDSPtr != nil {
		game.FrameDSPtr.DeleteData() // free old GL buffers
	}

	game.FrameDSPtr = dsPtr

}

// Redraw the game stage
func (game *Mki3dGame) Redraw() {
	gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) // to be moved to redraw ?
	// draw stage
	game.StageDSPtr.SetBackgroundColor()
	game.StageDSPtr.DrawStage()
	// draw frame
	game.FrameDSPtr.DrawModel()
	// draw tokens
	game.TokenDSPtr.DrawModel()
	// draw monsters
	game.MonsterDSPtr.DrawModel()

	// draw sectors
	gl.Disable(gl.DEPTH_TEST)
	game.SectorsDSPtr.DrawStage()
	gl.Enable(gl.DEPTH_TEST)

}

type Traveler struct {
	Position mgl32.Vec3 // position
	/* orientation */
	rotXZ float32 // horizontal rotation (in degrees)
	rotYZ float32 // vertical rotation (in degrees)

}
<|endoftext|>