<commit_before>package gofpdf

/*
 * Copyright (c) 2015 Kurt Jung (Gmail: kurt.w.jung),
 *   Marcus Downing, Jan Slabon (Setasign)
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

import (
	"sort"
)

// CreateTemplate defines a new template using the current page size.
func (f *Fpdf) CreateTemplate(fn func(*Tpl)) Template {
	return newTpl(PointType{0, 0}, f.curPageSize, f.unitStr, f.fontDirStr, fn, f)
}

// CreateTemplateCustom starts a template, using the given bounds.
func (f *Fpdf) CreateTemplateCustom(corner PointType, size SizeType, fn func(*Tpl)) Template {
	return newTpl(corner, size, f.unitStr, f.fontDirStr, fn, f)
}

// CreateTemplate creates a template not attached to any document
func CreateTemplate(corner PointType, size SizeType, unitStr, fontDirStr string, fn func(*Tpl)) Template {
	return newTpl(corner, size, unitStr, fontDirStr, fn, nil)
}

// UseTemplate adds a template to the current page or another template,
// using the size and position at which it was originally written.
func (f *Fpdf) UseTemplate(t Template) {
	if t == nil {
		f.SetErrorf("template is nil")
		return
	}
	corner, size := t.Size()
	f.UseTemplateScaled(t, corner, size)
}

// UseTemplateScaled adds a template to the current page or another template,
// using the given page coordinates.
func (f *Fpdf) UseTemplateScaled(t Template, corner PointType, size SizeType) {
	if t == nil {
		f.SetErrorf("template is nil")
		return
	}

	// You have to add at least a page first
	if f.page <= 0 {
		f.SetErrorf("cannot use a template without first adding a page")
		return
	}

	// make a note of the fact that we actually use this template, as well as any other templates,
	// images or fonts it uses
	f.templates[t.ID()] = t
	for _, tt := range t.Templates() {
		f.templates[tt.ID()] = tt
	}
	for name, ti := range t.Images() {
		name = sprintf("t%d-%s", t.ID(), name)
		f.images[name] = ti
	}

	// template data
	_, templateSize := t.Size()
	scaleX := size.Wd / templateSize.Wd
	scaleY := size.Ht / templateSize.Ht
	tx := corner.X * f.k
	ty := (f.curPageSize.Ht - corner.Y - size.Ht) * f.k

	f.outf("q %.4F 0 0 %.4F %.4F %.4F cm", scaleX, scaleY, tx, ty) // Translate
	f.outf("/TPL%d Do Q", t.ID())
}

var nextTemplateIDChannel = func() chan int64 {
	ch := make(chan int64)
	go func() {
		var nextID int64 = 1
		for {
			ch <- nextID
			nextID++
		}
	}()
	return ch
}()

// GenerateTemplateID gives the next template ID. These numbers are global so that they can never clash.
func GenerateTemplateID() int64 {
	return <-nextTemplateIDChannel
}

// Template is an object that can be written to, then used and re-used any number of times within a document.
type Template interface {
	ID() int64
	Size() (PointType, SizeType)
	Bytes() []byte
	Images() map[string]*ImageInfoType
	Templates() []Template
}

// putTemplates writes the templates to the PDF
func (f *Fpdf) putTemplates() {
	filter := ""
	if f.compress {
		filter = "/Filter /FlateDecode "
	}

	templates := sortTemplates(f.templates, f.catalogSort)
	var t Template
	for _, t = range templates {
		corner, size := t.Size()

		f.newobj()
		f.templateObjects[t.ID()] = f.n
		f.outf("<<%s/Type /XObject", filter)
		f.out("/Subtype /Form")
		f.out("/Formtype 1")
		f.outf("/BBox [%.2F %.2F %.2F %.2F]", corner.X*f.k, corner.Y*f.k, (corner.X+size.Wd)*f.k, (corner.Y+size.Ht)*f.k)
		if corner.X != 0 || corner.Y != 0 {
			f.outf("/Matrix [1 0 0 1 %.5F %.5F]", -corner.X*f.k*2, corner.Y*f.k*2)
		}

		// Template's resource dictionary
		f.out("/Resources ")
		f.out("<</ProcSet [/PDF /Text /ImageB /ImageC /ImageI]")

		tImages := t.Images()
		tTemplates := t.Templates()
		if len(tImages) > 0 || len(tTemplates) > 0 {
			f.out("/XObject <<")
			{
				var key string
				var keyList []string
				var ti *ImageInfoType
				for key = range tImages {
					keyList = append(keyList, key)
				}
				if gl.catalogSort {
					sort.Strings(keyList)
				}
				for _, key = range keyList {
					// for _, ti := range tImages {
					ti = tImages[key]
					f.outf("/I%d %d 0 R", ti.i, ti.n)
				}
			}
			for _, tt := range tTemplates {
				id := tt.ID()
				if objID, ok := f.templateObjects[id]; ok {
					f.outf("/TPL%d %d 0 R", id, objID)
				}
			}
			f.out(">>")
		}

		f.out(">>")

		//  Write the template's byte stream
		buffer := t.Bytes()
		// fmt.Println("Put template bytes", string(buffer[:]))
		if f.compress {
			buffer = sliceCompress(buffer)
		}
		f.outf("/Length %d >>", len(buffer))
		f.putstream(buffer)
		f.out("endobj")
	}
}

func templateKeyList(mp map[int64]Template, sort bool) (keyList []int64) {
	var key int64
	for key = range mp {
		keyList = append(keyList, key)
	}
	if sort {
		gensort(len(keyList),
			func(a, b int) bool {
				return keyList[a] < keyList[b]
			},
			func(a, b int) {
				keyList[a], keyList[b] = keyList[b], keyList[a]
			})
	}
	return
}

// sortTemplates puts templates in a suitable order based on dependices
func sortTemplates(templates map[int64]Template, catalogSort bool) []Template {
	chain := make([]Template, 0, len(templates)*2)

	// build a full set of dependency chains
	var keyList []int64
	var key int64
	var t Template
	keyList = templateKeyList(templates, catalogSort)
	for _, key = range keyList {
		t = templates[key]
		tlist := templateChainDependencies(t)
		for _, tt := range tlist {
			if tt != nil {
				chain = append(chain, tt)
			}
		}
	}

	// reduce that to make a simple list
	sorted := make([]Template, 0, len(templates))
chain:
	for _, t := range chain {
		for _, already := range sorted {
			if t == already {
				continue chain
			}
		}
		sorted = append(sorted, t)
	}

	return sorted
}

//  templateChainDependencies is a recursive function for determining the full chain of template dependencies
func templateChainDependencies(template Template) []Template {
	requires := template.Templates()
	chain := make([]Template, len(requires)*2)
	for _, req := range requires {
		for _, sub := range templateChainDependencies(req) {
			chain = append(chain, sub)
		}
	}
	chain = append(chain, template)
	return chain
}

// < 0002640  31 20 31 32 20 30 20 52  0a 2f 54 50 4c 32 20 31  |1 12 0 R./TPL2 1|
// < 0002650  35 20 30 20 52 0a 2f 54  50 4c 31 20 31 34 20 30  |5 0 R./TPL1 14 0|

// > 0002640  31 20 31 32 20 30 20 52  0a 2f 54 50 4c 31 20 31  |1 12 0 R./TPL1 1|
// > 0002650  34 20 30 20 52 0a 2f 54  50 4c 32 20 31 35 20 30  |4 0 R./TPL2 15 0|
<commit_msg>Use %f rather than %F since earlier versions of Go did not support the latter. Thanks, Marcus.<commit_after>package gofpdf

/*
 * Copyright (c) 2015 Kurt Jung (Gmail: kurt.w.jung),
 *   Marcus Downing, Jan Slabon (Setasign)
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

import (
	"sort"
)

// CreateTemplate defines a new template using the current page size.
func (f *Fpdf) CreateTemplate(fn func(*Tpl)) Template {
	return newTpl(PointType{0, 0}, f.curPageSize, f.unitStr, f.fontDirStr, fn, f)
}

// CreateTemplateCustom starts a template, using the given bounds.
func (f *Fpdf) CreateTemplateCustom(corner PointType, size SizeType, fn func(*Tpl)) Template {
	return newTpl(corner, size, f.unitStr, f.fontDirStr, fn, f)
}

// CreateTemplate creates a template not attached to any document
func CreateTemplate(corner PointType, size SizeType, unitStr, fontDirStr string, fn func(*Tpl)) Template {
	return newTpl(corner, size, unitStr, fontDirStr, fn, nil)
}

// UseTemplate adds a template to the current page or another template,
// using the size and position at which it was originally written.
func (f *Fpdf) UseTemplate(t Template) {
	if t == nil {
		f.SetErrorf("template is nil")
		return
	}
	corner, size := t.Size()
	f.UseTemplateScaled(t, corner, size)
}

// UseTemplateScaled adds a template to the current page or another template,
// using the given page coordinates.
func (f *Fpdf) UseTemplateScaled(t Template, corner PointType, size SizeType) {
	if t == nil {
		f.SetErrorf("template is nil")
		return
	}

	// You have to add at least a page first
	if f.page <= 0 {
		f.SetErrorf("cannot use a template without first adding a page")
		return
	}

	// make a note of the fact that we actually use this template, as well as any other templates,
	// images or fonts it uses
	f.templates[t.ID()] = t
	for _, tt := range t.Templates() {
		f.templates[tt.ID()] = tt
	}
	for name, ti := range t.Images() {
		name = sprintf("t%d-%s", t.ID(), name)
		f.images[name] = ti
	}

	// template data
	_, templateSize := t.Size()
	scaleX := size.Wd / templateSize.Wd
	scaleY := size.Ht / templateSize.Ht
	tx := corner.X * f.k
	ty := (f.curPageSize.Ht - corner.Y - size.Ht) * f.k

	f.outf("q %.4f 0 0 %.4f %.4f %.4f cm", scaleX, scaleY, tx, ty) // Translate
	f.outf("/TPL%d Do Q", t.ID())
}

var nextTemplateIDChannel = func() chan int64 {
	ch := make(chan int64)
	go func() {
		var nextID int64 = 1
		for {
			ch <- nextID
			nextID++
		}
	}()
	return ch
}()

// GenerateTemplateID gives the next template ID. These numbers are global so that they can never clash.
func GenerateTemplateID() int64 {
	return <-nextTemplateIDChannel
}

// Template is an object that can be written to, then used and re-used any number of times within a document.
type Template interface {
	ID() int64
	Size() (PointType, SizeType)
	Bytes() []byte
	Images() map[string]*ImageInfoType
	Templates() []Template
}

// putTemplates writes the templates to the PDF
func (f *Fpdf) putTemplates() {
	filter := ""
	if f.compress {
		filter = "/Filter /FlateDecode "
	}

	templates := sortTemplates(f.templates, f.catalogSort)
	var t Template
	for _, t = range templates {
		corner, size := t.Size()

		f.newobj()
		f.templateObjects[t.ID()] = f.n
		f.outf("<<%s/Type /XObject", filter)
		f.out("/Subtype /Form")
		f.out("/Formtype 1")
		f.outf("/BBox [%.2f %.2f %.2f %.2f]", corner.X*f.k, corner.Y*f.k, (corner.X+size.Wd)*f.k, (corner.Y+size.Ht)*f.k)
		if corner.X != 0 || corner.Y != 0 {
			f.outf("/Matrix [1 0 0 1 %.5f %.5f]", -corner.X*f.k*2, corner.Y*f.k*2)
		}

		// Template's resource dictionary
		f.out("/Resources ")
		f.out("<</ProcSet [/PDF /Text /ImageB /ImageC /ImageI]")

		tImages := t.Images()
		tTemplates := t.Templates()
		if len(tImages) > 0 || len(tTemplates) > 0 {
			f.out("/XObject <<")
			{
				var key string
				var keyList []string
				var ti *ImageInfoType
				for key = range tImages {
					keyList = append(keyList, key)
				}
				if gl.catalogSort {
					sort.Strings(keyList)
				}
				for _, key = range keyList {
					// for _, ti := range tImages {
					ti = tImages[key]
					f.outf("/I%d %d 0 R", ti.i, ti.n)
				}
			}
			for _, tt := range tTemplates {
				id := tt.ID()
				if objID, ok := f.templateObjects[id]; ok {
					f.outf("/TPL%d %d 0 R", id, objID)
				}
			}
			f.out(">>")
		}

		f.out(">>")

		//  Write the template's byte stream
		buffer := t.Bytes()
		// fmt.Println("Put template bytes", string(buffer[:]))
		if f.compress {
			buffer = sliceCompress(buffer)
		}
		f.outf("/Length %d >>", len(buffer))
		f.putstream(buffer)
		f.out("endobj")
	}
}

func templateKeyList(mp map[int64]Template, sort bool) (keyList []int64) {
	var key int64
	for key = range mp {
		keyList = append(keyList, key)
	}
	if sort {
		gensort(len(keyList),
			func(a, b int) bool {
				return keyList[a] < keyList[b]
			},
			func(a, b int) {
				keyList[a], keyList[b] = keyList[b], keyList[a]
			})
	}
	return
}

// sortTemplates puts templates in a suitable order based on dependices
func sortTemplates(templates map[int64]Template, catalogSort bool) []Template {
	chain := make([]Template, 0, len(templates)*2)

	// build a full set of dependency chains
	var keyList []int64
	var key int64
	var t Template
	keyList = templateKeyList(templates, catalogSort)
	for _, key = range keyList {
		t = templates[key]
		tlist := templateChainDependencies(t)
		for _, tt := range tlist {
			if tt != nil {
				chain = append(chain, tt)
			}
		}
	}

	// reduce that to make a simple list
	sorted := make([]Template, 0, len(templates))
chain:
	for _, t := range chain {
		for _, already := range sorted {
			if t == already {
				continue chain
			}
		}
		sorted = append(sorted, t)
	}

	return sorted
}

//  templateChainDependencies is a recursive function for determining the full chain of template dependencies
func templateChainDependencies(template Template) []Template {
	requires := template.Templates()
	chain := make([]Template, len(requires)*2)
	for _, req := range requires {
		for _, sub := range templateChainDependencies(req) {
			chain = append(chain, sub)
		}
	}
	chain = append(chain, template)
	return chain
}

// < 0002640  31 20 31 32 20 30 20 52  0a 2f 54 50 4c 32 20 31  |1 12 0 R./TPL2 1|
// < 0002650  35 20 30 20 52 0a 2f 54  50 4c 31 20 31 34 20 30  |5 0 R./TPL1 14 0|

// > 0002640  31 20 31 32 20 30 20 52  0a 2f 54 50 4c 31 20 31  |1 12 0 R./TPL1 1|
// > 0002650  34 20 30 20 52 0a 2f 54  50 4c 32 20 31 35 20 30  |4 0 R./TPL2 15 0|
<|endoftext|>


<commit_before>package main

import "sort"

type dungeonPath struct {
	dungeon   *dungeon
	neighbors [8]position
}

func (dp *dungeonPath) Neighbors(pos position) []position {
	nb := dp.neighbors[:0]
	return pos.Neighbors(nb, position.valid)
}

func (dp *dungeonPath) Cost(from, to position) int {
	if dp.dungeon.Cell(to).T == WallCell {
		return 4
	}
	return 1
}

func (dp *dungeonPath) Estimation(from, to position) int {
	return from.Distance(to)
}

type playerPath struct {
	game      *game
	neighbors [8]position
}

func (pp *playerPath) Neighbors(pos position) []position {
	d := pp.game.Dungeon
	nb := pp.neighbors[:0]
	keep := func(npos position) bool {
		return npos.valid() && d.Cell(npos).T != WallCell &&
			d.Cell(npos).Explored && !pp.game.UnknownDig[npos] && !pp.game.ExclusionsMap[npos]
	}
	if pp.game.Player.HasStatus(StatusConfusion) {
		nb = pos.CardinalNeighbors(nb, keep)
	} else {
		nb = pos.Neighbors(nb, keep)
	}
	return nb
}

func (pp *playerPath) Cost(from, to position) int {
	return 1
}

func (pp *playerPath) Estimation(from, to position) int {
	return from.Distance(to)
}

type noisePath struct {
	game      *game
	neighbors [8]position
}

func (fp *noisePath) Neighbors(pos position) []position {
	nb := fp.neighbors[:0]
	return pos.Neighbors(nb, position.valid)
}

func (fp *noisePath) Cost(from, to position) int {
	return 1
}

type normalPath struct {
	game      *game
	neighbors [8]position
}

func (np *normalPath) Neighbors(pos position) []position {
	nb := np.neighbors[:0]
	d := np.game.Dungeon
	keep := func(npos position) bool {
		return npos.valid() && d.Cell(npos).T != WallCell
	}
	if np.game.Player.HasStatus(StatusConfusion) {
		return pos.CardinalNeighbors(nb, keep)
	}
	return pos.Neighbors(nb, keep)
}

func (np *normalPath) Cost(from, to position) int {
	return 1
}

type autoexplorePath struct {
	game      *game
	neighbors [8]position
}

func (ap *autoexplorePath) Neighbors(pos position) []position {
	if ap.game.ExclusionsMap[pos] {
		return nil
	}
	d := ap.game.Dungeon
	nb := ap.neighbors[:0]
	keep := func(npos position) bool {
		return npos.valid() && d.Cell(npos).T != WallCell && !ap.game.ExclusionsMap[pos]
	}
	if ap.game.Player.HasStatus(StatusConfusion) {
		nb = pos.CardinalNeighbors(nb, keep)
	} else {
		nb = pos.Neighbors(nb, keep)
	}
	return nb
}

func (ap *autoexplorePath) Cost(from, to position) int {
	return 1
}

type monPath struct {
	game      *game
	monster   *monster
	wall      bool
	neighbors [8]position
}

func (mp *monPath) Neighbors(pos position) []position {
	nb := mp.neighbors[:0]
	d := mp.game.Dungeon
	keep := func(npos position) bool {
		return npos.valid() && (d.Cell(npos).T != WallCell || mp.wall)
	}
	if mp.monster.Status(MonsConfused) {
		return pos.CardinalNeighbors(nb, keep)
	}
	return pos.Neighbors(nb, keep)
}

func (mp *monPath) Cost(from, to position) int {
	g := mp.game
	mons, _ := g.MonsterAt(to)
	if !mons.Exists() {
		if mp.wall && g.Dungeon.Cell(to).T == WallCell && mp.monster.State != Hunting {
			return 6
		}
		return 1
	}
	return 4
}

func (mp *monPath) Estimation(from, to position) int {
	return from.Distance(to)
}

func (m *monster) APath(g *game, from, to position) []position {
	mp := &monPath{game: g, monster: m}
	if m.Kind == MonsEarthDragon {
		mp.wall = true
	}
	path, _, found := AstarPath(mp, from, to)
	if !found {
		return nil
	}
	return path
}

func (g *game) PlayerPath(from, to position) []position {
	pp := &playerPath{game: g}
	path, _, found := AstarPath(pp, from, to)
	if !found {
		return nil
	}
	return path
}

func (g *game) SortedNearestTo(cells []position, to position) []position {
	ps := posSlice{}
	for _, pos := range cells {
		pp := &playerPath{game: g}
		_, cost, found := AstarPath(pp, pos, to)
		if found {
			ps = append(ps, posCost{pos, cost})
		}
	}
	sort.Sort(ps)
	sorted := []position{}
	for _, pc := range ps {
		sorted = append(sorted, pc.pos)
	}
	return sorted
}

type posCost struct {
	pos  position
	cost int
}

type posSlice []posCost

func (ps posSlice) Len() int           { return len(ps) }
func (ps posSlice) Swap(i, j int)      { ps[i], ps[j] = ps[j], ps[i] }
func (ps posSlice) Less(i, j int) bool { return ps[i].cost < ps[j].cost }
<commit_msg>fix regression in monster noise detection<commit_after>package main

import "sort"

type dungeonPath struct {
	dungeon   *dungeon
	neighbors [8]position
}

func (dp *dungeonPath) Neighbors(pos position) []position {
	nb := dp.neighbors[:0]
	return pos.Neighbors(nb, position.valid)
}

func (dp *dungeonPath) Cost(from, to position) int {
	if dp.dungeon.Cell(to).T == WallCell {
		return 4
	}
	return 1
}

func (dp *dungeonPath) Estimation(from, to position) int {
	return from.Distance(to)
}

type playerPath struct {
	game      *game
	neighbors [8]position
}

func (pp *playerPath) Neighbors(pos position) []position {
	d := pp.game.Dungeon
	nb := pp.neighbors[:0]
	keep := func(npos position) bool {
		return npos.valid() && d.Cell(npos).T != WallCell &&
			d.Cell(npos).Explored && !pp.game.UnknownDig[npos] && !pp.game.ExclusionsMap[npos]
	}
	if pp.game.Player.HasStatus(StatusConfusion) {
		nb = pos.CardinalNeighbors(nb, keep)
	} else {
		nb = pos.Neighbors(nb, keep)
	}
	return nb
}

func (pp *playerPath) Cost(from, to position) int {
	return 1
}

func (pp *playerPath) Estimation(from, to position) int {
	return from.Distance(to)
}

type noisePath struct {
	game      *game
	neighbors [8]position
}

func (fp *noisePath) Neighbors(pos position) []position {
	nb := fp.neighbors[:0]
	d := fp.game.Dungeon
	keep := func(npos position) bool {
		return npos.valid() && d.Cell(npos).T != WallCell
	}
	return pos.Neighbors(nb, keep)
}

func (fp *noisePath) Cost(from, to position) int {
	return 1
}

type normalPath struct {
	game      *game
	neighbors [8]position
}

func (np *normalPath) Neighbors(pos position) []position {
	nb := np.neighbors[:0]
	d := np.game.Dungeon
	keep := func(npos position) bool {
		return npos.valid() && d.Cell(npos).T != WallCell
	}
	if np.game.Player.HasStatus(StatusConfusion) {
		return pos.CardinalNeighbors(nb, keep)
	}
	return pos.Neighbors(nb, keep)
}

func (np *normalPath) Cost(from, to position) int {
	return 1
}

type autoexplorePath struct {
	game      *game
	neighbors [8]position
}

func (ap *autoexplorePath) Neighbors(pos position) []position {
	if ap.game.ExclusionsMap[pos] {
		return nil
	}
	d := ap.game.Dungeon
	nb := ap.neighbors[:0]
	keep := func(npos position) bool {
		return npos.valid() && d.Cell(npos).T != WallCell && !ap.game.ExclusionsMap[pos]
	}
	if ap.game.Player.HasStatus(StatusConfusion) {
		nb = pos.CardinalNeighbors(nb, keep)
	} else {
		nb = pos.Neighbors(nb, keep)
	}
	return nb
}

func (ap *autoexplorePath) Cost(from, to position) int {
	return 1
}

type monPath struct {
	game      *game
	monster   *monster
	wall      bool
	neighbors [8]position
}

func (mp *monPath) Neighbors(pos position) []position {
	nb := mp.neighbors[:0]
	d := mp.game.Dungeon
	keep := func(npos position) bool {
		return npos.valid() && (d.Cell(npos).T != WallCell || mp.wall)
	}
	if mp.monster.Status(MonsConfused) {
		return pos.CardinalNeighbors(nb, keep)
	}
	return pos.Neighbors(nb, keep)
}

func (mp *monPath) Cost(from, to position) int {
	g := mp.game
	mons, _ := g.MonsterAt(to)
	if !mons.Exists() {
		if mp.wall && g.Dungeon.Cell(to).T == WallCell && mp.monster.State != Hunting {
			return 6
		}
		return 1
	}
	return 4
}

func (mp *monPath) Estimation(from, to position) int {
	return from.Distance(to)
}

func (m *monster) APath(g *game, from, to position) []position {
	mp := &monPath{game: g, monster: m}
	if m.Kind == MonsEarthDragon {
		mp.wall = true
	}
	path, _, found := AstarPath(mp, from, to)
	if !found {
		return nil
	}
	return path
}

func (g *game) PlayerPath(from, to position) []position {
	pp := &playerPath{game: g}
	path, _, found := AstarPath(pp, from, to)
	if !found {
		return nil
	}
	return path
}

func (g *game) SortedNearestTo(cells []position, to position) []position {
	ps := posSlice{}
	for _, pos := range cells {
		pp := &playerPath{game: g}
		_, cost, found := AstarPath(pp, pos, to)
		if found {
			ps = append(ps, posCost{pos, cost})
		}
	}
	sort.Sort(ps)
	sorted := []position{}
	for _, pc := range ps {
		sorted = append(sorted, pc.pos)
	}
	return sorted
}

type posCost struct {
	pos  position
	cost int
}

type posSlice []posCost

func (ps posSlice) Len() int           { return len(ps) }
func (ps posSlice) Swap(i, j int)      { ps[i], ps[j] = ps[j], ps[i] }
func (ps posSlice) Less(i, j int) bool { return ps[i].cost < ps[j].cost }
<|endoftext|>


<commit_before>package seccomp

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"github.com/subgraph/oz"
	"github.com/subgraph/oz/fs"
	"golang.org/x/sys/unix"
	"io"
	"os"
	"os/exec"
	"os/user"
	"path"
	"syscall"
)

// #include "sys/ptrace.h"
import "C"

const (
	STRINGARG = iota + 1
	PTRARG
	INTARG
)

type SystemCallArgs []int

func Tracer() {

	var train = false
	var cmd string
	var cmdArgs []string
	var p *oz.Profile

	var noprofile = flag.Bool("train", false, "Training mode")
	var debug = flag.Bool("debug", false, "Debug")
	var appendpolicy = flag.Bool("append", false, "Append to existing policy if exists")
	var trainingoutput = flag.String("output", "", "Training policy output file")

	flag.Parse()

	var args = flag.Args()

	if *noprofile == true {
		train = true
		cmd = "/usr/bin/oz-seccomp"
		cmdArgs = append([]string{"-mode=train"}, args...)
	} else {
		p = new(oz.Profile)
		if err := json.NewDecoder(os.Stdin).Decode(&p); err != nil {
			log.Error("unable to decode profile data: %v", err)
			os.Exit(1)
		}
		if p.Seccomp.Mode == oz.PROFILE_SECCOMP_TRAIN {
			train = true
		}
		*debug = p.Seccomp.Debug
		cmd = args[0]
		cmdArgs = args[1:]
	}

	var cpid = 0
	done := false

	log.Info("Tracer running command (%v) arguments (%v)\n", cmd, cmdArgs)
	c := exec.Command(cmd)
	c.SysProcAttr = &syscall.SysProcAttr{Ptrace: true}
	c.Env = os.Environ()
	c.Args = append(c.Args, cmdArgs...)

	if *noprofile == false {

		pi, err := c.StdinPipe()
		if err != nil {
			fmt.Errorf("error creating stdin pipe for tracer process: %v", err)
			os.Exit(1)
		}
		jdata, err := json.Marshal(p)
		if err != nil {
			fmt.Errorf("Unable to marshal seccomp state: %+v", err)
			os.Exit(1)
		}
		io.Copy(pi, bytes.NewBuffer(jdata))
		pi.Close()
	}
	children := make(map[int]bool)
	renderFunctions := getRenderingFunctions()

	trainingset := make(map[int]bool)
	trainingargs := make(map[int]map[int][]uint)

	if err := c.Start(); err == nil {
		cpid = c.Process.Pid
		children[c.Process.Pid] = true
		var s syscall.WaitStatus
		pid, err := syscall.Wait4(-1, &s, syscall.WALL, nil)
		children[pid] = true
		if err != nil {
			log.Error("Error (wait4) err:%v pid:%i", err, pid)
		}
		log.Info("Tracing child pid: %v\n", pid)
		for done == false {
			pflags := unix.PTRACE_O_TRACESECCOMP
			pflags |= unix.PTRACE_O_TRACEFORK
			pflags |= unix.PTRACE_O_TRACEVFORK
			pflags |= unix.PTRACE_O_TRACECLONE
			pflags |= C.PTRACE_O_EXITKILL

			syscall.PtraceSetOptions(pid, pflags)
			syscall.PtraceCont(pid, 0)
			pid, err = syscall.Wait4(-1, &s, syscall.WALL, nil)
			if err != nil {
				log.Error("Error (wait4) err:%v pid:%i children:%v\n", err, pid, children)
				done = true
				continue
			}
			children[pid] = true
			if s.Exited() == true {
				delete(children, pid)
				log.Info("Child pid %v finished.\n", pid)
				if len(children) == 0 {
					done = true
				}
				continue
			}
			if s.Signaled() == true {
				log.Error("Pid signaled, pid: %v signal: %v", pid, s)
				delete(children, pid)
				continue
			}
			switch uint32(s) >> 8 {

			case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_SECCOMP << 8):
				var regs syscall.PtraceRegs
				err = syscall.PtraceGetRegs(pid, &regs)

				if err != nil {
					log.Error("Error (ptrace): %v", err)
				}

				systemcall, err := syscallByNum(getSyscallNumber(regs))
				if err != nil {
					log.Error("Error: %v", err)
					continue
				}

				/* Render the system call invocation */

				r := getSyscallRegisterArgs(regs)
				call := ""

				if train == true {
					trainingset[getSyscallNumber(regs)] = true
					if systemcall.captureArgs != nil {
						for c, i := range systemcall.captureArgs {
							if i == 1 {
								if trainingargs[getSyscallNumber(regs)] == nil {
									trainingargs[getSyscallNumber(regs)] = make(map[int][]uint)
								}
								if contains(trainingargs[getSyscallNumber(regs)][c], uint(r[c])) == false {
									trainingargs[getSyscallNumber(regs)][c] = append(trainingargs[getSyscallNumber(regs)][c], uint(r[c]))
								}
							}
						}
					}
				}

				if f, ok := renderFunctions[getSyscallNumber(regs)]; ok {
					call, err = f(pid, r)
					if err != nil {
						log.Info("%v", err)
						continue
					}
					if *debug == true {
						call += "\n  " + renderSyscallBasic(pid, systemcall, regs)
					}
				} else {
					call = renderSyscallBasic(pid, systemcall, regs)
				}

				log.Info("seccomp hit on sandbox pid %v (%v) syscall %v (%v):\n  %s", pid, getProcessCmdLine(pid), systemcall.name, systemcall.num, call)
				continue

			case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_EXIT << 8):
				if *debug == true {
					log.Error("Ptrace exit event detected pid %v (%s)", pid, getProcessCmdLine(pid))
				}
				delete(children, pid)
				continue

			case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_CLONE << 8):
				newpid, err := syscall.PtraceGetEventMsg(pid)
				if err != nil {
					log.Error("PTrace event message retrieval failed: %v", err)
				}
				children[int(newpid)] = true
				if *debug == true {
					log.Error("Ptrace clone event detected pid %v (%s)", pid, getProcessCmdLine(pid))
				}
				continue
			case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_FORK << 8):
				if *debug == true {
					log.Error("PTrace fork event detected pid %v (%s)", pid, getProcessCmdLine(pid))
				}
				newpid, err := syscall.PtraceGetEventMsg(pid)
				if err != nil {
					log.Error("PTrace event message retrieval failed: %v", err)
				}
				children[int(newpid)] = true
				continue
			case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_VFORK << 8):
				if *debug == true {
					log.Error("Ptrace vfork event detected pid %v (%s)", pid, getProcessCmdLine(pid))
				}
				newpid, err := syscall.PtraceGetEventMsg(pid)
				if err != nil {
					log.Error("PTrace event message retrieval failed: %v", err)
				}
				children[int(newpid)] = true
				continue
			case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_VFORK_DONE << 8):
				if *debug == true {
					log.Error("Ptrace vfork done event detected pid %v (%s)", pid, getProcessCmdLine(pid))
				}
				newpid, err := syscall.PtraceGetEventMsg(pid)
				if err != nil {
					log.Error("PTrace event message retrieval failed: %v", err)
				}
				children[int(newpid)] = true

				continue
			case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_EXEC << 8):
				if *debug == true {
					log.Error("Ptrace exec event detected pid %v (%s)", pid, getProcessCmdLine(pid))
				}
				continue
			case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_STOP << 8):
				if *debug == true {
					log.Error("Ptrace stop event detected pid %v (%s)", pid, getProcessCmdLine(pid))
				}
				continue
			case uint32(unix.SIGTRAP):
				if *debug == true {
					log.Error("SIGTRAP detected in pid %v (%s)", pid, getProcessCmdLine(pid))
				}
				continue
			case uint32(unix.SIGCHLD):
				if *debug == true {
					log.Error("SIGCHLD detected pid %v (%s)", pid, getProcessCmdLine(pid))
				}
				continue
			case uint32(unix.SIGSTOP):
				if *debug == true {
					log.Error("SIGSTOP detected pid %v (%s)", pid, getProcessCmdLine(pid))
				}
				continue
			case uint32(unix.SIGSEGV):
				if *debug == true {
					log.Error("SIGSEGV detected pid %v (%s)", pid, getProcessCmdLine(pid))
				}
				err = syscall.Kill(pid, 9)
				if err != nil {
					log.Error("kill: %v", err)
					os.Exit(1)
				}
				delete(children, pid)
				continue
			default:
				y := s.StopSignal()
				if *debug == true {
					log.Error("Child stopped for unknown reasons pid %v status %v signal %i (%s)", pid, s, y, getProcessCmdLine(pid))
				}
				continue
			}
		}

		if train == true {
			var u *user.User
			var e error
			u, e = user.Current()
			var resolvedpath = ""

			if e != nil {
				log.Error("user.Current(): %v", e)
			}

			if *trainingoutput != "" {
				resolvedpath = *trainingoutput
			} else {
				if *noprofile == false {
					resolvedpath, e = fs.ResolvePathNoGlob(p.Seccomp.TrainOutput, u)
					if e != nil {
						log.Error("resolveVars(): %v", e)
					}
				} else {
					s := fmt.Sprintf("${HOME}/%s-%d.seccomp", fname(os.Args[2]), cpid)
					resolvedpath, e = fs.ResolvePathNoGlob(s, u)
				}
			}
			policyout := "execve:1\n"
			for call := range trainingset {
				done := false
				for c := range trainingargs {
					if c == call {
						for a, v := range trainingargs[c] {
							sc, _ := syscallByNum(call)
							policyout += fmt.Sprintf("%s:%s\n", sc.name, genArgs(uint(a), (v)))
							done = true
						}
					}
				}
				if done == false {
					sc, _ := syscallByNum(call)
					policyout += fmt.Sprintf("%s:1\n", sc.name)
				}
			}
			if *appendpolicy == true {
				log.Error("Not yet implemented.")
			}
			f, err := os.OpenFile(resolvedpath, os.O_CREATE|os.O_RDWR, 0600)
			if err == nil {
				_, err := f.WriteString(policyout)
				if err != nil {
					log.Error("Error writing policy file: %v", err)
				}
				err = f.Close()
				if err != nil {
					log.Error("Error closing policy file: %v", err)
				}
			} else {
				log.Error("Error opening policy file \"%s\": %v", resolvedpath, err)
			}
		}
	}
}

func genArgs(a uint, vals []uint) string {
	s := ""
	for idx, x := range vals {
		s += fmt.Sprintf(" arg%d == %d ", a, x)
		if idx < len(vals)-1 {
			s += "||"
		}
	}
	return s
}

func contains(slice []uint, val uint) bool {
	for _, x := range slice {
		if val == x {
			return true
		}
	}
	return false
}
func fname(p string) string {
	_, fname := path.Split(p)
	return fname
}
<commit_msg>Add TODO.<commit_after>package seccomp

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"github.com/subgraph/oz"
	"github.com/subgraph/oz/fs"
	"golang.org/x/sys/unix"
	"io"
	"os"
	"os/exec"
	"os/user"
	"path"
	"syscall"
)

// #include "sys/ptrace.h"
import "C"

const (
	STRINGARG = iota + 1
	PTRARG
	INTARG
)

type SystemCallArgs []int

func Tracer() {

	var train = false
	var cmd string
	var cmdArgs []string
	var p *oz.Profile

	var noprofile = flag.Bool("train", false, "Training mode")
	var debug = flag.Bool("debug", false, "Debug")
	var appendpolicy = flag.Bool("append", false, "Append to existing policy if exists")
	var trainingoutput = flag.String("output", "", "Training policy output file")

	flag.Parse()

	var args = flag.Args()

	if *noprofile == true {
		train = true

		// TODO: remove hardcoded path and read prefix from /etc/oz.conf

		cmd = "/usr/bin/oz-seccomp"
		cmdArgs = append([]string{"-mode=train"}, args...)
	} else {
		p = new(oz.Profile)
		if err := json.NewDecoder(os.Stdin).Decode(&p); err != nil {
			log.Error("unable to decode profile data: %v", err)
			os.Exit(1)
		}
		if p.Seccomp.Mode == oz.PROFILE_SECCOMP_TRAIN {
			train = true
		}
		*debug = p.Seccomp.Debug
		cmd = args[0]
		cmdArgs = args[1:]
	}

	var cpid = 0
	done := false

	log.Info("Tracer running command (%v) arguments (%v)\n", cmd, cmdArgs)
	c := exec.Command(cmd)
	c.SysProcAttr = &syscall.SysProcAttr{Ptrace: true}
	c.Env = os.Environ()
	c.Args = append(c.Args, cmdArgs...)

	if *noprofile == false {

		pi, err := c.StdinPipe()
		if err != nil {
			fmt.Errorf("error creating stdin pipe for tracer process: %v", err)
			os.Exit(1)
		}
		jdata, err := json.Marshal(p)
		if err != nil {
			fmt.Errorf("Unable to marshal seccomp state: %+v", err)
			os.Exit(1)
		}
		io.Copy(pi, bytes.NewBuffer(jdata))
		pi.Close()
	}
	children := make(map[int]bool)
	renderFunctions := getRenderingFunctions()

	trainingset := make(map[int]bool)
	trainingargs := make(map[int]map[int][]uint)

	if err := c.Start(); err == nil {
		cpid = c.Process.Pid
		children[c.Process.Pid] = true
		var s syscall.WaitStatus
		pid, err := syscall.Wait4(-1, &s, syscall.WALL, nil)
		children[pid] = true
		if err != nil {
			log.Error("Error (wait4) err:%v pid:%i", err, pid)
		}
		log.Info("Tracing child pid: %v\n", pid)
		for done == false {
			pflags := unix.PTRACE_O_TRACESECCOMP
			pflags |= unix.PTRACE_O_TRACEFORK
			pflags |= unix.PTRACE_O_TRACEVFORK
			pflags |= unix.PTRACE_O_TRACECLONE
			pflags |= C.PTRACE_O_EXITKILL

			syscall.PtraceSetOptions(pid, pflags)
			syscall.PtraceCont(pid, 0)
			pid, err = syscall.Wait4(-1, &s, syscall.WALL, nil)
			if err != nil {
				log.Error("Error (wait4) err:%v pid:%i children:%v\n", err, pid, children)
				done = true
				continue
			}
			children[pid] = true
			if s.Exited() == true {
				delete(children, pid)
				log.Info("Child pid %v finished.\n", pid)
				if len(children) == 0 {
					done = true
				}
				continue
			}
			if s.Signaled() == true {
				log.Error("Pid signaled, pid: %v signal: %v", pid, s)
				delete(children, pid)
				continue
			}
			switch uint32(s) >> 8 {

			case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_SECCOMP << 8):
				var regs syscall.PtraceRegs
				err = syscall.PtraceGetRegs(pid, &regs)

				if err != nil {
					log.Error("Error (ptrace): %v", err)
				}

				systemcall, err := syscallByNum(getSyscallNumber(regs))
				if err != nil {
					log.Error("Error: %v", err)
					continue
				}

				/* Render the system call invocation */

				r := getSyscallRegisterArgs(regs)
				call := ""

				if train == true {
					trainingset[getSyscallNumber(regs)] = true
					if systemcall.captureArgs != nil {
						for c, i := range systemcall.captureArgs {
							if i == 1 {
								if trainingargs[getSyscallNumber(regs)] == nil {
									trainingargs[getSyscallNumber(regs)] = make(map[int][]uint)
								}
								if contains(trainingargs[getSyscallNumber(regs)][c], uint(r[c])) == false {
									trainingargs[getSyscallNumber(regs)][c] = append(trainingargs[getSyscallNumber(regs)][c], uint(r[c]))
								}
							}
						}
					}
				}

				if f, ok := renderFunctions[getSyscallNumber(regs)]; ok {
					call, err = f(pid, r)
					if err != nil {
						log.Info("%v", err)
						continue
					}
					if *debug == true {
						call += "\n  " + renderSyscallBasic(pid, systemcall, regs)
					}
				} else {
					call = renderSyscallBasic(pid, systemcall, regs)
				}

				log.Info("seccomp hit on sandbox pid %v (%v) syscall %v (%v):\n  %s", pid, getProcessCmdLine(pid), systemcall.name, systemcall.num, call)
				continue

			case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_EXIT << 8):
				if *debug == true {
					log.Error("Ptrace exit event detected pid %v (%s)", pid, getProcessCmdLine(pid))
				}
				delete(children, pid)
				continue

			case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_CLONE << 8):
				newpid, err := syscall.PtraceGetEventMsg(pid)
				if err != nil {
					log.Error("PTrace event message retrieval failed: %v", err)
				}
				children[int(newpid)] = true
				if *debug == true {
					log.Error("Ptrace clone event detected pid %v (%s)", pid, getProcessCmdLine(pid))
				}
				continue
			case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_FORK << 8):
				if *debug == true {
					log.Error("PTrace fork event detected pid %v (%s)", pid, getProcessCmdLine(pid))
				}
				newpid, err := syscall.PtraceGetEventMsg(pid)
				if err != nil {
					log.Error("PTrace event message retrieval failed: %v", err)
				}
				children[int(newpid)] = true
				continue
			case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_VFORK << 8):
				if *debug == true {
					log.Error("Ptrace vfork event detected pid %v (%s)", pid, getProcessCmdLine(pid))
				}
				newpid, err := syscall.PtraceGetEventMsg(pid)
				if err != nil {
					log.Error("PTrace event message retrieval failed: %v", err)
				}
				children[int(newpid)] = true
				continue
			case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_VFORK_DONE << 8):
				if *debug == true {
					log.Error("Ptrace vfork done event detected pid %v (%s)", pid, getProcessCmdLine(pid))
				}
				newpid, err := syscall.PtraceGetEventMsg(pid)
				if err != nil {
					log.Error("PTrace event message retrieval failed: %v", err)
				}
				children[int(newpid)] = true

				continue
			case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_EXEC << 8):
				if *debug == true {
					log.Error("Ptrace exec event detected pid %v (%s)", pid, getProcessCmdLine(pid))
				}
				continue
			case uint32(unix.SIGTRAP) | (unix.PTRACE_EVENT_STOP << 8):
				if *debug == true {
					log.Error("Ptrace stop event detected pid %v (%s)", pid, getProcessCmdLine(pid))
				}
				continue
			case uint32(unix.SIGTRAP):
				if *debug == true {
					log.Error("SIGTRAP detected in pid %v (%s)", pid, getProcessCmdLine(pid))
				}
				continue
			case uint32(unix.SIGCHLD):
				if *debug == true {
					log.Error("SIGCHLD detected pid %v (%s)", pid, getProcessCmdLine(pid))
				}
				continue
			case uint32(unix.SIGSTOP):
				if *debug == true {
					log.Error("SIGSTOP detected pid %v (%s)", pid, getProcessCmdLine(pid))
				}
				continue
			case uint32(unix.SIGSEGV):
				if *debug == true {
					log.Error("SIGSEGV detected pid %v (%s)", pid, getProcessCmdLine(pid))
				}
				err = syscall.Kill(pid, 9)
				if err != nil {
					log.Error("kill: %v", err)
					os.Exit(1)
				}
				delete(children, pid)
				continue
			default:
				y := s.StopSignal()
				if *debug == true {
					log.Error("Child stopped for unknown reasons pid %v status %v signal %i (%s)", pid, s, y, getProcessCmdLine(pid))
				}
				continue
			}
		}

		if train == true {
			var u *user.User
			var e error
			u, e = user.Current()
			var resolvedpath = ""

			if e != nil {
				log.Error("user.Current(): %v", e)
			}

			if *trainingoutput != "" {
				resolvedpath = *trainingoutput
			} else {
				if *noprofile == false {
					resolvedpath, e = fs.ResolvePathNoGlob(p.Seccomp.TrainOutput, u)
					if e != nil {
						log.Error("resolveVars(): %v", e)
					}
				} else {
					s := fmt.Sprintf("${HOME}/%s-%d.seccomp", fname(os.Args[2]), cpid)
					resolvedpath, e = fs.ResolvePathNoGlob(s, u)
				}
			}
			policyout := "execve:1\n"
			for call := range trainingset {
				done := false
				for c := range trainingargs {
					if c == call {
						for a, v := range trainingargs[c] {
							sc, _ := syscallByNum(call)
							policyout += fmt.Sprintf("%s:%s\n", sc.name, genArgs(uint(a), (v)))
							done = true
						}
					}
				}
				if done == false {
					sc, _ := syscallByNum(call)
					policyout += fmt.Sprintf("%s:1\n", sc.name)
				}
			}
			if *appendpolicy == true {
				log.Error("Not yet implemented.")
			}
			f, err := os.OpenFile(resolvedpath, os.O_CREATE|os.O_RDWR, 0600)
			if err == nil {
				_, err := f.WriteString(policyout)
				if err != nil {
					log.Error("Error writing policy file: %v", err)
				}
				err = f.Close()
				if err != nil {
					log.Error("Error closing policy file: %v", err)
				}
			} else {
				log.Error("Error opening policy file \"%s\": %v", resolvedpath, err)
			}
		}
	}
}

func genArgs(a uint, vals []uint) string {
	s := ""
	for idx, x := range vals {
		s += fmt.Sprintf(" arg%d == %d ", a, x)
		if idx < len(vals)-1 {
			s += "||"
		}
	}
	return s
}

func contains(slice []uint, val uint) bool {
	for _, x := range slice {
		if val == x {
			return true
		}
	}
	return false
}
func fname(p string) string {
	_, fname := path.Split(p)
	return fname
}
<|endoftext|>


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

package dep

import (
	"errors"
	"reflect"
	"strings"
	"testing"

	"github.com/golang/dep/internal/gps"
	"github.com/golang/dep/internal/test"
)

func TestReadManifest(t *testing.T) {
	h := test.NewHelper(t)
	defer h.Cleanup()

	mf := h.GetTestFile("manifest/golden.toml")
	defer mf.Close()
	got, _, err := readManifest(mf)
	if err != nil {
		t.Fatalf("Should have read Manifest correctly, but got err %q", err)
	}

	c, _ := gps.NewSemverConstraint("^0.12.0")
	want := Manifest{
		Constraints: map[gps.ProjectRoot]gps.ProjectProperties{
			gps.ProjectRoot("github.com/golang/dep/internal/gps"): {
				Constraint: c,
			},
			gps.ProjectRoot("github.com/babble/brook"): {
				Constraint: gps.Revision("d05d5aca9f895d19e9265839bffeadd74a2d2ecb"),
			},
		},
		Ovr: map[gps.ProjectRoot]gps.ProjectProperties{
			gps.ProjectRoot("github.com/golang/dep/internal/gps"): {
				Source:     "https://github.com/golang/dep/internal/gps",
				Constraint: gps.NewBranch("master"),
			},
		},
		Ignored: []string{"github.com/foo/bar"},
	}

	if !reflect.DeepEqual(got.Constraints, want.Constraints) {
		t.Error("Valid manifest's dependencies did not parse as expected")
	}
	if !reflect.DeepEqual(got.Ovr, want.Ovr) {
		t.Error("Valid manifest's overrides did not parse as expected")
	}
	if !reflect.DeepEqual(got.Ignored, want.Ignored) {
		t.Error("Valid manifest's ignored did not parse as expected")
	}
}

func TestWriteManifest(t *testing.T) {
	h := test.NewHelper(t)
	defer h.Cleanup()

	golden := "manifest/golden.toml"
	want := h.GetTestFileString(golden)
	c, _ := gps.NewSemverConstraint("^0.12.0")
	m := &Manifest{
		Constraints: map[gps.ProjectRoot]gps.ProjectProperties{
			gps.ProjectRoot("github.com/golang/dep/internal/gps"): {
				Constraint: c,
			},
			gps.ProjectRoot("github.com/babble/brook"): {
				Constraint: gps.Revision("d05d5aca9f895d19e9265839bffeadd74a2d2ecb"),
			},
		},
		Ovr: map[gps.ProjectRoot]gps.ProjectProperties{
			gps.ProjectRoot("github.com/golang/dep/internal/gps"): {
				Source:     "https://github.com/golang/dep/internal/gps",
				Constraint: gps.NewBranch("master"),
			},
		},
		Ignored: []string{"github.com/foo/bar"},
	}

	got, err := m.MarshalTOML()
	if err != nil {
		t.Fatalf("Error while marshaling valid manifest to TOML: %q", err)
	}

	if string(got) != want {
		if *test.UpdateGolden {
			if err = h.WriteTestFile(golden, string(got)); err != nil {
				t.Fatal(err)
			}
		} else {
			t.Errorf("Valid manifest did not marshal to TOML as expected:\n\t(GOT): %s\n\t(WNT): %s", string(got), want)
		}
	}
}

func TestReadManifestErrors(t *testing.T) {
	h := test.NewHelper(t)
	defer h.Cleanup()
	var err error

	tests := []struct {
		name string
		file string
	}{
		{"multiple constraints", "manifest/error1.toml"},
		{"multiple dependencies", "manifest/error2.toml"},
	}

	for _, tst := range tests {
		mf := h.GetTestFile(tst.file)
		defer mf.Close()
		_, _, err = readManifest(mf)
		if err == nil {
			t.Errorf("Reading manifest with %s should have caused error, but did not", tst.name)
		} else if !strings.Contains(err.Error(), tst.name) {
			t.Errorf("Unexpected error %q; expected %s error", err, tst.name)
		}
	}
}

func TestValidateManifest(t *testing.T) {
	cases := []struct {
		tomlString string
		wantWarn   []error
		wantError  error
	}{
		{
			tomlString: `
			required = ["github.com/foo/bar"]
			`,
			wantWarn:  []error{},
			wantError: nil,
		},
		{
			tomlString: `
			required = "github.com/foo/bar"
			`,
			wantWarn:  []error{},
			wantError: errInvalidRequired,
		},
		{
			tomlString: `
			required = [1, 2, 3]
			`,
			wantWarn:  []error{},
			wantError: errInvalidRequired,
		},
		{
			tomlString: `
			[[required]]
			  name = "foo"
			`,
			wantWarn:  []error{},
			wantError: errInvalidRequired,
		},
		{
			tomlString: `
			ignored = ["foo"]
			`,
			wantWarn:  []error{},
			wantError: nil,
		},
		{
			tomlString: `
			ignored = "foo"
			`,
			wantWarn:  []error{},
			wantError: errInvalidIgnored,
		},
		{
			tomlString: `
			ignored = [1, 2, 3]
			`,
			wantWarn:  []error{},
			wantError: errInvalidIgnored,
		},
		{
			tomlString: `
			[[ignored]]
			  name = "foo"
			`,
			wantWarn:  []error{},
			wantError: errInvalidIgnored,
		},
		{
			tomlString: `
			[metadata]
			  authors = "foo"
			  version = "1.0.0"
			`,
			wantWarn:  []error{},
			wantError: nil,
		},
		{
			tomlString: `
			foo = "some-value"
			version = 14

			[[bar]]
			  author = "xyz"

			[[constraint]]
			  name = "github.com/foo/bar"
			  version = ""
			`,
			wantWarn: []error{
				errors.New("Unknown field in manifest: foo"),
				errors.New("Unknown field in manifest: bar"),
				errors.New("Unknown field in manifest: version"),
			},
			wantError: nil,
		},
		{
			tomlString: `
			metadata = "project-name"

			[[constraint]]
			  name = "github.com/foo/bar"
			`,
			wantWarn:  []error{errors.New("metadata should be a TOML table")},
			wantError: nil,
		},
		{
			tomlString: `
			[[constraint]]
			  name = "github.com/foo/bar"
			`,
			wantWarn:  []error{},
			wantError: nil,
		},
		{
			tomlString: `
			constraint = "foo"
			`,
			wantWarn:  []error{},
			wantError: errInvalidConstraint,
		},
		{
			tomlString: `
			constraint = ["foo", "bar"]
			`,
			wantWarn:  []error{},
			wantError: errInvalidConstraint,
		},
		{
			tomlString: `
			[[override]]
			  name = "github.com/foo/bar"
			`,
			wantWarn:  []error{},
			wantError: nil,
		},
		{
			tomlString: `
			override = "bar"
			`,
			wantWarn:  []error{},
			wantError: errInvalidOverride,
		},
		{
			tomlString: `
			override = ["foo", "bar"]
			`,
			wantWarn:  []error{},
			wantError: errInvalidOverride,
		},
		{
			tomlString: `
			[[constraint]]
			  name = "github.com/foo/bar"
			  location = "some-value"
			  link = "some-other-value"
			  metadata = "foo"

			[[override]]
			  nick = "foo"
			`,
			wantWarn: []error{
				errors.New("Invalid key \"location\" in \"constraint\""),
				errors.New("Invalid key \"link\" in \"constraint\""),
				errors.New("Invalid key \"nick\" in \"override\""),
				errors.New("metadata in \"constraint\" should be a TOML table"),
			},
			wantError: nil,
		},
		{
			tomlString: `
			[[constraint]]
			  name = "github.com/foo/bar"

			  [constraint.metadata]
			    color = "blue"
			`,
			wantWarn:  []error{},
			wantError: nil,
		},
		{
			tomlString: `
			[[constraint]]
			  name = "github.com/foo/bar"
			  revision = "b86ad16"
			`,
			wantWarn:  []error{errors.New("revision \"b86ad16\" should not be in abbreviated form")},
			wantError: nil,
		},
		{
			tomlString: `
			[[constraint]]
			  name = "foobar.com/hg"
			  revision = "8d43f8c0b836"
			`,
			wantWarn:  []error{errors.New("revision \"8d43f8c0b836\" should not be in abbreviated form")},
			wantError: nil,
		},
	}

	// contains for error
	contains := func(s []error, e error) bool {
		for _, a := range s {
			if a.Error() == e.Error() {
				return true
			}
		}
		return false
	}

	for _, c := range cases {
		errs, err := validateManifest(c.tomlString)

		// compare validation errors
		if err != c.wantError {
			t.Fatalf("Manifest errors are not as expected: \n\t(GOT) %v \n\t(WNT) %v", err, c.wantError)
		}

		// compare length of error slice
		if len(errs) != len(c.wantWarn) {
			t.Fatalf("Number of manifest errors are not as expected: \n\t(GOT) %v errors(%v)\n\t(WNT) %v errors(%v).", len(errs), errs, len(c.wantWarn), c.wantWarn)
		}

		// check if the expected errors exist in actual errors slice
		for _, er := range errs {
			if !contains(c.wantWarn, er) {
				t.Fatalf("Manifest errors are not as expected: \n\t(MISSING) %v\n\t(FROM) %v", er, c.wantWarn)
			}
		}
	}
}
<commit_msg>Add tests for array length check<commit_after>// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package dep

import (
	"errors"
	"reflect"
	"strings"
	"testing"

	"github.com/golang/dep/internal/gps"
	"github.com/golang/dep/internal/test"
)

func TestReadManifest(t *testing.T) {
	h := test.NewHelper(t)
	defer h.Cleanup()

	mf := h.GetTestFile("manifest/golden.toml")
	defer mf.Close()
	got, _, err := readManifest(mf)
	if err != nil {
		t.Fatalf("Should have read Manifest correctly, but got err %q", err)
	}

	c, _ := gps.NewSemverConstraint("^0.12.0")
	want := Manifest{
		Constraints: map[gps.ProjectRoot]gps.ProjectProperties{
			gps.ProjectRoot("github.com/golang/dep/internal/gps"): {
				Constraint: c,
			},
			gps.ProjectRoot("github.com/babble/brook"): {
				Constraint: gps.Revision("d05d5aca9f895d19e9265839bffeadd74a2d2ecb"),
			},
		},
		Ovr: map[gps.ProjectRoot]gps.ProjectProperties{
			gps.ProjectRoot("github.com/golang/dep/internal/gps"): {
				Source:     "https://github.com/golang/dep/internal/gps",
				Constraint: gps.NewBranch("master"),
			},
		},
		Ignored: []string{"github.com/foo/bar"},
	}

	if !reflect.DeepEqual(got.Constraints, want.Constraints) {
		t.Error("Valid manifest's dependencies did not parse as expected")
	}
	if !reflect.DeepEqual(got.Ovr, want.Ovr) {
		t.Error("Valid manifest's overrides did not parse as expected")
	}
	if !reflect.DeepEqual(got.Ignored, want.Ignored) {
		t.Error("Valid manifest's ignored did not parse as expected")
	}
}

func TestWriteManifest(t *testing.T) {
	h := test.NewHelper(t)
	defer h.Cleanup()

	golden := "manifest/golden.toml"
	want := h.GetTestFileString(golden)
	c, _ := gps.NewSemverConstraint("^0.12.0")
	m := &Manifest{
		Constraints: map[gps.ProjectRoot]gps.ProjectProperties{
			gps.ProjectRoot("github.com/golang/dep/internal/gps"): {
				Constraint: c,
			},
			gps.ProjectRoot("github.com/babble/brook"): {
				Constraint: gps.Revision("d05d5aca9f895d19e9265839bffeadd74a2d2ecb"),
			},
		},
		Ovr: map[gps.ProjectRoot]gps.ProjectProperties{
			gps.ProjectRoot("github.com/golang/dep/internal/gps"): {
				Source:     "https://github.com/golang/dep/internal/gps",
				Constraint: gps.NewBranch("master"),
			},
		},
		Ignored: []string{"github.com/foo/bar"},
	}

	got, err := m.MarshalTOML()
	if err != nil {
		t.Fatalf("Error while marshaling valid manifest to TOML: %q", err)
	}

	if string(got) != want {
		if *test.UpdateGolden {
			if err = h.WriteTestFile(golden, string(got)); err != nil {
				t.Fatal(err)
			}
		} else {
			t.Errorf("Valid manifest did not marshal to TOML as expected:\n\t(GOT): %s\n\t(WNT): %s", string(got), want)
		}
	}
}

func TestReadManifestErrors(t *testing.T) {
	h := test.NewHelper(t)
	defer h.Cleanup()
	var err error

	tests := []struct {
		name string
		file string
	}{
		{"multiple constraints", "manifest/error1.toml"},
		{"multiple dependencies", "manifest/error2.toml"},
	}

	for _, tst := range tests {
		mf := h.GetTestFile(tst.file)
		defer mf.Close()
		_, _, err = readManifest(mf)
		if err == nil {
			t.Errorf("Reading manifest with %s should have caused error, but did not", tst.name)
		} else if !strings.Contains(err.Error(), tst.name) {
			t.Errorf("Unexpected error %q; expected %s error", err, tst.name)
		}
	}
}

func TestValidateManifest(t *testing.T) {
	cases := []struct {
		tomlString string
		wantWarn   []error
		wantError  error
	}{
		{
			tomlString: `
			required = ["github.com/foo/bar"]
			`,
			wantWarn:  []error{},
			wantError: nil,
		},
		{
			tomlString: `
			required = "github.com/foo/bar"
			`,
			wantWarn:  []error{},
			wantError: errInvalidRequired,
		},
		{
			tomlString: `
			required = []
			`,
			wantWarn:  []error{},
			wantError: nil,
		},
		{
			tomlString: `
			required = [1, 2, 3]
			`,
			wantWarn:  []error{},
			wantError: errInvalidRequired,
		},
		{
			tomlString: `
			[[required]]
			  name = "foo"
			`,
			wantWarn:  []error{},
			wantError: errInvalidRequired,
		},
		{
			tomlString: `
			ignored = ["foo"]
			`,
			wantWarn:  []error{},
			wantError: nil,
		},
		{
			tomlString: `
			ignored = "foo"
			`,
			wantWarn:  []error{},
			wantError: errInvalidIgnored,
		},
		{
			tomlString: `
			ignored = []
			`,
			wantWarn:  []error{},
			wantError: nil,
		},
		{
			tomlString: `
			ignored = [1, 2, 3]
			`,
			wantWarn:  []error{},
			wantError: errInvalidIgnored,
		},
		{
			tomlString: `
			[[ignored]]
			  name = "foo"
			`,
			wantWarn:  []error{},
			wantError: errInvalidIgnored,
		},
		{
			tomlString: `
			[metadata]
			  authors = "foo"
			  version = "1.0.0"
			`,
			wantWarn:  []error{},
			wantError: nil,
		},
		{
			tomlString: `
			foo = "some-value"
			version = 14

			[[bar]]
			  author = "xyz"

			[[constraint]]
			  name = "github.com/foo/bar"
			  version = ""
			`,
			wantWarn: []error{
				errors.New("Unknown field in manifest: foo"),
				errors.New("Unknown field in manifest: bar"),
				errors.New("Unknown field in manifest: version"),
			},
			wantError: nil,
		},
		{
			tomlString: `
			metadata = "project-name"

			[[constraint]]
			  name = "github.com/foo/bar"
			`,
			wantWarn:  []error{errors.New("metadata should be a TOML table")},
			wantError: nil,
		},
		{
			tomlString: `
			[[constraint]]
			  name = "github.com/foo/bar"
			`,
			wantWarn:  []error{},
			wantError: nil,
		},
		{
			tomlString: `
			[[constraint]]
			`,
			wantWarn:  []error{},
			wantError: nil,
		},
		{
			tomlString: `
			constraint = "foo"
			`,
			wantWarn:  []error{},
			wantError: errInvalidConstraint,
		},
		{
			tomlString: `
			constraint = ["foo", "bar"]
			`,
			wantWarn:  []error{},
			wantError: errInvalidConstraint,
		},
		{
			tomlString: `
			[[override]]
			  name = "github.com/foo/bar"
			`,
			wantWarn:  []error{},
			wantError: nil,
		},
		{
			tomlString: `
			[[override]]
			`,
			wantWarn:  []error{},
			wantError: nil,
		},
		{
			tomlString: `
			override = "bar"
			`,
			wantWarn:  []error{},
			wantError: errInvalidOverride,
		},
		{
			tomlString: `
			override = ["foo", "bar"]
			`,
			wantWarn:  []error{},
			wantError: errInvalidOverride,
		},
		{
			tomlString: `
			[[constraint]]
			  name = "github.com/foo/bar"
			  location = "some-value"
			  link = "some-other-value"
			  metadata = "foo"

			[[override]]
			  nick = "foo"
			`,
			wantWarn: []error{
				errors.New("Invalid key \"location\" in \"constraint\""),
				errors.New("Invalid key \"link\" in \"constraint\""),
				errors.New("Invalid key \"nick\" in \"override\""),
				errors.New("metadata in \"constraint\" should be a TOML table"),
			},
			wantError: nil,
		},
		{
			tomlString: `
			[[constraint]]
			  name = "github.com/foo/bar"

			  [constraint.metadata]
			    color = "blue"
			`,
			wantWarn:  []error{},
			wantError: nil,
		},
		{
			tomlString: `
			[[constraint]]
			  name = "github.com/foo/bar"
			  revision = "b86ad16"
			`,
			wantWarn:  []error{errors.New("revision \"b86ad16\" should not be in abbreviated form")},
			wantError: nil,
		},
		{
			tomlString: `
			[[constraint]]
			  name = "foobar.com/hg"
			  revision = "8d43f8c0b836"
			`,
			wantWarn:  []error{errors.New("revision \"8d43f8c0b836\" should not be in abbreviated form")},
			wantError: nil,
		},
	}

	// contains for error
	contains := func(s []error, e error) bool {
		for _, a := range s {
			if a.Error() == e.Error() {
				return true
			}
		}
		return false
	}

	for _, c := range cases {
		errs, err := validateManifest(c.tomlString)

		// compare validation errors
		if err != c.wantError {
			t.Fatalf("Manifest errors are not as expected: \n\t(GOT) %v \n\t(WNT) %v", err, c.wantError)
		}

		// compare length of error slice
		if len(errs) != len(c.wantWarn) {
			t.Fatalf("Number of manifest errors are not as expected: \n\t(GOT) %v errors(%v)\n\t(WNT) %v errors(%v).", len(errs), errs, len(c.wantWarn), c.wantWarn)
		}

		// check if the expected errors exist in actual errors slice
		for _, er := range errs {
			if !contains(c.wantWarn, er) {
				t.Fatalf("Manifest errors are not as expected: \n\t(MISSING) %v\n\t(FROM) %v", er, c.wantWarn)
			}
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"errors"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"sync"
)

var (
	output    = flag.String("o", "-", "output file")
	workers   = flag.Int("n", 4, "parallel connections")
	chunkSize = flag.Int("c", 100*1024, "chunk size")
	debug     = flag.Bool("d", false, "debug")
)

type chunk struct {
	offset int64
	size   int64
	reader io.ReadCloser
}

type download struct {
	wg     sync.WaitGroup
	url    string
	size   int64
	writer io.Writer
}

func newDownload(url string) (*download, error) {
	size, err := contentSize(url)
	if err != nil {
		return nil, err
	}

	return &download{
		url:    url,
		size:   size,
		writer: os.Stdout,
	}, nil
}

// chunkFeeder generates chunk structures and sends them to the out channel.
// The chunk size is determined via commandline params. The last chunk is
// is sized accordingly.
func (d *download) chunkFeeder(out chan<- *chunk) {
	if *debug {
		log.Printf("Feeder is running")
	}
	size := int64(*chunkSize)
	for i := int64(0); ; i++ {
		if i*size >= d.size {
			break
		}
		if i*size+size > d.size {
			size = i*size + size - d.size
		}
		out <- &chunk{
			offset: i * size,
			size:   size}
	}
	if *debug {
		log.Printf("Feeder is closing")
	}
	close(out)
}

type FileDeleter struct {
	*os.File
}

func (f FileDeleter) Close() error {
	os.Remove(f.Name())
	return f.File.Close()
}

// chunkWorker task chunks from the in channel, fetches the chunk from
// the http resource into the buffer in the chunk structure.
// The completed chunk is then sent to the out channel.
// Any error causes to panic.
func (d *download) chunkWorker(n int, in <-chan *chunk, out chan<- *chunk) {
	if *debug {
		log.Println("Started chunk worker", n)
	}

	for ch := range in {
		if *debug {
			log.Printf("Chunk worker %d fetching chunk %d (%d)\n", n, ch.offset, ch.size)
		}
		body, err := fetch(d.url, ch.offset, ch.size)
		if err != nil {
			log.Fatal(err)
		}

		// TODO(mkm) check errors
		func() {
			var writer io.Writer
			if *output == "-" {
				var buffer bytes.Buffer
				writer = &buffer
				ch.reader = ioutil.NopCloser(&buffer)
			} else {
				f, err := os.Create(fmt.Sprintf("%s.chunk-%d", *output, ch.offset))
				if err != nil {
					log.Fatalf("Cannot create temp file for chunk for offset %d, %s", ch.offset, err)
				}
				writer = f
				ch.reader = FileDeleter{f}
				defer func() {
					f.Seek(0, 0)
				}()
			}
			io.Copy(writer, body)
			body.Close()

			// ensure that other deferreds get called before we enqueue it.
			defer func() {
				out <- ch
			}()
		}()
	}

	if *debug {
		log.Println("Finishing chunk worker", n)
	}
}

// chunkWriter reads completed chunks from the in channel,
// and writes them out in the correct order.
// Out of order chunks are kept in memory until the missing pieces
// are delivered.
func (d *download) chunkWriter(in <-chan *chunk) {
	readyChunks := make(map[int64]*chunk)
	outPos := int64(0)

	for ch := range in {
		if *debug {
			log.Printf("Chunk writer processing completed chunk %d (%d)\n", ch.offset, ch.size)
		}
		readyChunks[ch.offset] = ch

		for {
			if c, ok := readyChunks[outPos]; ok {
				// TODO(mkm) check for errors
				io.Copy(d.writer, c.reader)
				c.reader.Close()
				outPos += c.size
			} else {
				break
			}
		}
	}
}

// start
func start() error {
	url := flag.Arg(0)

	d, err := newDownload(url)
	if err != nil {
		return err
	}

	if *output != "-" {
		f, err := os.Create(*output)
		if err != nil {
			return err
		}
		defer f.Close()
		d.writer = f
	}

	if *debug {
		log.Println("Size", d.size)
	}

	ich := make(chan *chunk, 2)
	och := make(chan *chunk, 2)

	// spawn the chunk feeder.
	go d.chunkFeeder(ich)

	// spawn the workers in such a way
	// that we know when they are all done.
	for i := 0; i < *workers; i++ {
		d.wg.Add(1)
		go func(i int) {
			d.chunkWorker(i, ich, och)
			d.wg.Done()
		}(i)
	}

	// when all the workers are gone
	// close the chunk writer input
	go func() {
		d.wg.Wait()
		close(och)
	}()

	// the chunk writer block until
	// all the chunks are received, reordered and written.
	d.chunkWriter(och)

	return nil
}

// contentSize returns the size of the resource
// at the given url.
func contentSize(url string) (int64, error) {
	resp, err := http.Get(url)
	if err != nil {
		return 0, err
	}
	return resp.ContentLength, nil
}

// followRedirect ensures that all headers are passed through the redirect.
// This is necessary for the range header to work across redirects.
func followRedirect(req *http.Request, via []*http.Request) error {
	req.Header = via[0].Header
	return nil
}

// fetch returns a ReadCloser with the content of a
// section of the resource found at url starting at from
// and len long.
func fetch(url string, from, len int64) (io.ReadCloser, error) {
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}

	req.Header.Add("Range", fmt.Sprintf("bytes=%d-%d", from, from+len-1))

	client := &http.Client{
		CheckRedirect: followRedirect,
	}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusPartialContent {
		defer resp.Body.Close()
		if resp.StatusCode == http.StatusOK {
			return nil, fmt.Errorf("Server doesn't implement partial content")
		}
		return nil, errors.New(resp.Status)
	}
	return resp.Body, nil
}

func main() {
	flag.Parse()
	if err := start(); err != nil {
		log.Fatal(err)
	}
}
<commit_msg>Use temp files also when streaming to stdout<commit_after>package main

import (
	"errors"
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"sync"
)

var (
	output    = flag.String("o", "-", "output file")
	workers   = flag.Int("n", 4, "parallel connections")
	chunkSize = flag.Int("c", 100*1024, "chunk size")
	debug     = flag.Bool("d", false, "debug")
)

type chunk struct {
	offset int64
	size   int64
	reader io.ReadCloser
}

type download struct {
	wg     sync.WaitGroup
	url    string
	size   int64
	writer io.Writer
}

func newDownload(url string) (*download, error) {
	size, err := contentSize(url)
	if err != nil {
		return nil, err
	}

	return &download{
		url:    url,
		size:   size,
		writer: os.Stdout,
	}, nil
}

// chunkFeeder generates chunk structures and sends them to the out channel.
// The chunk size is determined via commandline params. The last chunk is
// is sized accordingly.
func (d *download) chunkFeeder(out chan<- *chunk) {
	if *debug {
		log.Printf("Feeder is running")
	}
	size := int64(*chunkSize)
	for i := int64(0); ; i++ {
		if i*size >= d.size {
			break
		}
		if i*size+size > d.size {
			size = i*size + size - d.size
		}
		out <- &chunk{
			offset: i * size,
			size:   size}
	}
	if *debug {
		log.Printf("Feeder is closing")
	}
	close(out)
}

type FileDeleter struct {
	*os.File
}

func (f FileDeleter) Close() error {
	os.Remove(f.Name())
	return f.File.Close()
}

// chunkWorker task chunks from the in channel, fetches the chunk from
// the http resource into the buffer in the chunk structure.
// The completed chunk is then sent to the out channel.
// Any error causes to panic.
func (d *download) chunkWorker(n int, in <-chan *chunk, out chan<- *chunk) {
	if *debug {
		log.Println("Started chunk worker", n)
	}

	for ch := range in {
		if *debug {
			log.Printf("Chunk worker %d fetching chunk %d (%d)\n", n, ch.offset, ch.size)
		}
		body, err := fetch(d.url, ch.offset, ch.size)
		if err != nil {
			log.Fatal(err)
		}

		// TODO(mkm) check errors
		basename := *output
		if *output == "-" {
			basename = "/tmp/pget.tmp"
		}

		f, err := os.Create(fmt.Sprintf("%s.chunk-%d", basename, ch.offset))
		if err != nil {
			log.Fatalf("Cannot create temp file for chunk for offset %d, %s", ch.offset, err)
		}

		io.Copy(f, body)
		body.Close()
		f.Seek(0, 0)

		ch.reader = FileDeleter{f}
		out <- ch
	}

	if *debug {
		log.Println("Finishing chunk worker", n)
	}
}

// chunkWriter reads completed chunks from the in channel,
// and writes them out in the correct order.
// Out of order chunks are kept in memory until the missing pieces
// are delivered.
func (d *download) chunkWriter(in <-chan *chunk) {
	readyChunks := make(map[int64]*chunk)
	outPos := int64(0)

	for ch := range in {
		if *debug {
			log.Printf("Chunk writer processing completed chunk %d (%d)\n", ch.offset, ch.size)
		}
		readyChunks[ch.offset] = ch

		for {
			if c, ok := readyChunks[outPos]; ok {
				// TODO(mkm) check for errors
				io.Copy(d.writer, c.reader)
				c.reader.Close()
				outPos += c.size
			} else {
				break
			}
		}
	}
}

// start
func start() error {
	url := flag.Arg(0)

	d, err := newDownload(url)
	if err != nil {
		return err
	}

	if *output != "-" {
		f, err := os.Create(*output)
		if err != nil {
			return err
		}
		defer f.Close()
		d.writer = f
	}

	if *debug {
		log.Println("Size", d.size)
	}

	ich := make(chan *chunk, 2)
	och := make(chan *chunk, 2)

	// spawn the chunk feeder.
	go d.chunkFeeder(ich)

	// spawn the workers in such a way
	// that we know when they are all done.
	for i := 0; i < *workers; i++ {
		d.wg.Add(1)
		go func(i int) {
			d.chunkWorker(i, ich, och)
			d.wg.Done()
		}(i)
	}

	// when all the workers are gone
	// close the chunk writer input
	go func() {
		d.wg.Wait()
		close(och)
	}()

	// the chunk writer block until
	// all the chunks are received, reordered and written.
	d.chunkWriter(och)

	return nil
}

// contentSize returns the size of the resource
// at the given url.
func contentSize(url string) (int64, error) {
	resp, err := http.Get(url)
	if err != nil {
		return 0, err
	}
	return resp.ContentLength, nil
}

// followRedirect ensures that all headers are passed through the redirect.
// This is necessary for the range header to work across redirects.
func followRedirect(req *http.Request, via []*http.Request) error {
	req.Header = via[0].Header
	return nil
}

// fetch returns a ReadCloser with the content of a
// section of the resource found at url starting at from
// and len long.
func fetch(url string, from, len int64) (io.ReadCloser, error) {
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}

	req.Header.Add("Range", fmt.Sprintf("bytes=%d-%d", from, from+len-1))

	client := &http.Client{
		CheckRedirect: followRedirect,
	}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusPartialContent {
		defer resp.Body.Close()
		if resp.StatusCode == http.StatusOK {
			return nil, fmt.Errorf("Server doesn't implement partial content")
		}
		return nil, errors.New(resp.Status)
	}
	return resp.Body, nil
}

func main() {
	flag.Parse()
	if err := start(); err != nil {
		log.Fatal(err)
	}
}
<|endoftext|>


<commit_before>/*
   PulseHA - HA Cluster Daemon
   Copyright (C) 2017-2018  Andrew Zak <andrew@pulseha.com>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published
   by the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package main

import (
	"encoding/json"
	"github.com/Syleron/PulseHA/src/utils"
	log "github.com/Sirupsen/logrus"
	"io/ioutil"
	"os"
)

type Config struct {
	Pulse     Local               `json:"pulseha"`
	Groups    map[string][]string `json:"floating_ip_groups"`
	Nodes     map[string]Node     `json:"nodes"`
	Logging   Logging             `json:"logging"`
}

type Local struct {
	TLS bool `json:"tls"`
	HealthCheckInterval   int             `json:"hcs_interval"`
	FailOverInterval      int             `json:"fos_interval"`
	FailOverLimit         int             `json:"fo_limit"`
}

type Nodes struct {
	Nodes map[string]Node
}

type Node struct {
	IP       string              `json:"bind_address"`
	Port     string              `json:"bind_port"`
	IPGroups map[string][]string `json:"group_assignments"`
}

type Logging struct {
	Level     string `json:"level"`
	ToLogFile bool   `json:"to_logfile"`
	LogFile   string `json:"logfile"`
}

/**
 * Returns a copy of the config
 */
func (c *Config) GetConfig() Config {
	return *c
}

/**
 * Sets the local node name
 */
func (c *Config) setLocalNode() error {
	hostname := utils.GetHostname()
	log.Debugf("Config:setLocalNode Hostname is: %s", hostname)
	c.localNode = hostname
	return nil
}

/**

 */
func (c *Config) nodeCount() int {
	return len(c.Nodes)
}

/**
 * Return the local node name
 */
func (c *Config) getLocalNode() string {
	return c.localNode
}

/**
 * Function used to load the config
 */
func (c *Config) Load() {
	log.Info("Loading configuration file")
	b, err := ioutil.ReadFile("/etc/pulseha/config.json")
	if err != nil {
		log.Errorf("Error reading config file: %s", err)
		os.Exit(1)
	}
	err = json.Unmarshal([]byte(b), &c)
	gconf.Config = *c
	if err != nil {
		log.Errorf("Unable to unmarshal config: %s", err)
		os.Exit(1)
	}
	if err != nil {
		log.Error("Unable to load config.json. Does it exist?")
		os.Exit(1)
	}
	err = c.setLocalNode()
	if err != nil {
		log.Fatalf("The local Hostname does not match the configuration")
	}
}

/**
 * Function used to save the config
 */
func (c *Config) Save() {
	log.Debug("Saving config..")
	gconf.Lock()
	defer gconf.Unlock()
	// Validate before we save
	c.Validate()
	// Convert struct back to JSON format
	configJSON, _ := json.MarshalIndent(c, "", "    ")
	// Save back to file
	err := ioutil.WriteFile("/etc/pulseha/config.json", configJSON, 0644)
	// Check for errors
	if err != nil {
		log.Error("Unable to save config.json. Does it exist?")
		os.Exit(1)
	}
}

/**
 * Reload the config file into memory.
 * Note: Need to clear memory value before calling Load()
 */
func (c *Config) Reload() {
	log.Debug("Reloading PulseHA config")
	// Reload the config file
	c.Load()
}

/**
 *
 */
func (c *Config) Validate() {
	var success bool = true

	// if we are in a cluster.. does our hostname exist?
	if c.ClusterCheck() {
		for name, _ := range c.Nodes {
			if _, ok := c.Nodes[name]; !ok {
				log.Error("Hostname mistmatch. Localhost does not exist in cluster config.")
				success = false
			}
		}
	}

	// TODO: Check if our hostname exists inthe cluster config
	// TODO: Check if we have valid network interface names

	// Handles if shit hits the roof
	if success == false {
		// log why we exited?
		os.Exit(0)
	}
}

/**
 *
 */
func (c *Config) LocalNode() Node {
	return c.Nodes[utils.GetHostname()]
}

/**
 * Private - Check to see if we are in a configured cluster or not.
 */
func (c *Config) ClusterCheck() bool {
	config := gconf.GetConfig()
	if len(config.Nodes) > 0 {
		return true
	}
	return false
}

/**
 * Return the total number of configured nodes we have in our config.
 */
func (c *Config) ClusterTotal() int {
	config := gconf.GetConfig()
	return len(config.Nodes)
}

/**
Returns the interface the group is assigned to
*/
func (c *Config) GetGroupIface(node string, groupName string) string {
	for nodeName, n := range c.Nodes {
		if nodeName == node {
			for iface, groups := range n.IPGroups {
				for _, group := range groups {
					if group == groupName {
						return iface
					}
				}
			}
		}
	}
	return ""
}

/**
 *
 */
func DefaultLocalConfig() *Config {
	return &Config{
	//Cluster: {
	//	//ClusterName: GetHostname(),
	//	//BindIP: "0.0.0.0",
	//	//BindPort: "8443",
	//},
	}
}
<commit_msg>resolved build error<commit_after>/*
   PulseHA - HA Cluster Daemon
   Copyright (C) 2017-2018  Andrew Zak <andrew@pulseha.com>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published
   by the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package main

import (
	"encoding/json"
	"github.com/Syleron/PulseHA/src/utils"
	log "github.com/Sirupsen/logrus"
	"io/ioutil"
	"os"
)

type Config struct {
	Pulse     Local               `json:"pulseha"`
	Groups    map[string][]string `json:"floating_ip_groups"`
	Nodes     map[string]Node     `json:"nodes"`
	Logging   Logging             `json:"logging"`
	localNode string
}

type Local struct {
	TLS bool `json:"tls"`
	HealthCheckInterval   int             `json:"hcs_interval"`
	FailOverInterval      int             `json:"fos_interval"`
	FailOverLimit         int             `json:"fo_limit"`
}

type Nodes struct {
	Nodes map[string]Node
}

type Node struct {
	IP       string              `json:"bind_address"`
	Port     string              `json:"bind_port"`
	IPGroups map[string][]string `json:"group_assignments"`
}

type Logging struct {
	Level     string `json:"level"`
	ToLogFile bool   `json:"to_logfile"`
	LogFile   string `json:"logfile"`
}

/**
 * Returns a copy of the config
 */
func (c *Config) GetConfig() Config {
	return *c
}

/**
 * Sets the local node name
 */
func (c *Config) setLocalNode() error {
	hostname := utils.GetHostname()
	log.Debugf("Config:setLocalNode Hostname is: %s", hostname)
	c.localNode = hostname
	return nil
}

/**

 */
func (c *Config) nodeCount() int {
	return len(c.Nodes)
}

/**
 * Return the local node name
 */
func (c *Config) getLocalNode() string {
	return c.localNode
}

/**
 * Function used to load the config
 */
func (c *Config) Load() {
	log.Info("Loading configuration file")
	b, err := ioutil.ReadFile("/etc/pulseha/config.json")
	if err != nil {
		log.Errorf("Error reading config file: %s", err)
		os.Exit(1)
	}
	err = json.Unmarshal([]byte(b), &c)
	gconf.Config = *c
	if err != nil {
		log.Errorf("Unable to unmarshal config: %s", err)
		os.Exit(1)
	}
	if err != nil {
		log.Error("Unable to load config.json. Does it exist?")
		os.Exit(1)
	}
	err = c.setLocalNode()
	if err != nil {
		log.Fatalf("The local Hostname does not match the configuration")
	}
}

/**
 * Function used to save the config
 */
func (c *Config) Save() {
	log.Debug("Saving config..")
	gconf.Lock()
	defer gconf.Unlock()
	// Validate before we save
	c.Validate()
	// Convert struct back to JSON format
	configJSON, _ := json.MarshalIndent(c, "", "    ")
	// Save back to file
	err := ioutil.WriteFile("/etc/pulseha/config.json", configJSON, 0644)
	// Check for errors
	if err != nil {
		log.Error("Unable to save config.json. Does it exist?")
		os.Exit(1)
	}
}

/**
 * Reload the config file into memory.
 * Note: Need to clear memory value before calling Load()
 */
func (c *Config) Reload() {
	log.Debug("Reloading PulseHA config")
	// Reload the config file
	c.Load()
}

/**
 *
 */
func (c *Config) Validate() {
	var success bool = true

	// if we are in a cluster.. does our hostname exist?
	if c.ClusterCheck() {
		for name, _ := range c.Nodes {
			if _, ok := c.Nodes[name]; !ok {
				log.Error("Hostname mistmatch. Localhost does not exist in cluster config.")
				success = false
			}
		}
	}

	// TODO: Check if our hostname exists inthe cluster config
	// TODO: Check if we have valid network interface names

	// Handles if shit hits the roof
	if success == false {
		// log why we exited?
		os.Exit(0)
	}
}

/**
 *
 */
func (c *Config) LocalNode() Node {
	return c.Nodes[utils.GetHostname()]
}

/**
 * Private - Check to see if we are in a configured cluster or not.
 */
func (c *Config) ClusterCheck() bool {
	config := gconf.GetConfig()
	if len(config.Nodes) > 0 {
		return true
	}
	return false
}

/**
 * Return the total number of configured nodes we have in our config.
 */
func (c *Config) ClusterTotal() int {
	config := gconf.GetConfig()
	return len(config.Nodes)
}

/**
Returns the interface the group is assigned to
*/
func (c *Config) GetGroupIface(node string, groupName string) string {
	for nodeName, n := range c.Nodes {
		if nodeName == node {
			for iface, groups := range n.IPGroups {
				for _, group := range groups {
					if group == groupName {
						return iface
					}
				}
			}
		}
	}
	return ""
}

/**
 *
 */
func DefaultLocalConfig() *Config {
	return &Config{
	//Cluster: {
	//	//ClusterName: GetHostname(),
	//	//BindIP: "0.0.0.0",
	//	//BindPort: "8443",
	//},
	}
}
<|endoftext|>


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

// Package frontend contains the Machine Database AppEngine front end.
package frontend

import (
	"net/http"

	_ "github.com/go-sql-driver/mysql"

	"go.chromium.org/luci/appengine/gaemiddleware/standard"
	"go.chromium.org/luci/common/data/rand/mathrand"
	"go.chromium.org/luci/grpc/discovery"
	"go.chromium.org/luci/grpc/prpc"
	"go.chromium.org/luci/server/router"

	"go.chromium.org/luci/machine-db/api/crimson/v1"
	"go.chromium.org/luci/machine-db/appengine/config"
	"go.chromium.org/luci/machine-db/appengine/database"
	"go.chromium.org/luci/machine-db/appengine/rpc"
)

func init() {
	mathrand.SeedRandomly()
	databaseMiddleware := standard.Base().Extend(database.WithMiddleware)

	r := router.New()
	standard.InstallHandlers(r)
	config.InstallHandlers(r, databaseMiddleware)
	r.GET("/", standard.Base(), handler)

	api := prpc.Server{}
	crimson.RegisterCrimsonServer(&api, rpc.NewServer())
	discovery.Enable(&api)
	api.InstallHandlers(r, databaseMiddleware)

	http.DefaultServeMux.Handle("/", r)
}

func handler(c *router.Context) {
	c.Writer.Header().Set("Content-Type", "text/plain")
	c.Writer.WriteHeader(http.StatusOK)
}
<commit_msg>[Machine Database] Report pRPC tsmon metrics<commit_after>// Copyright 2017 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package frontend contains the Machine Database AppEngine front end.
package frontend

import (
	"net/http"

	_ "github.com/go-sql-driver/mysql"

	"go.chromium.org/luci/appengine/gaemiddleware/standard"
	"go.chromium.org/luci/common/data/rand/mathrand"
	"go.chromium.org/luci/grpc/discovery"
	"go.chromium.org/luci/grpc/grpcmon"
	"go.chromium.org/luci/grpc/prpc"
	"go.chromium.org/luci/server/router"

	"go.chromium.org/luci/machine-db/api/crimson/v1"
	"go.chromium.org/luci/machine-db/appengine/config"
	"go.chromium.org/luci/machine-db/appengine/database"
	"go.chromium.org/luci/machine-db/appengine/rpc"
)

func init() {
	mathrand.SeedRandomly()
	databaseMiddleware := standard.Base().Extend(database.WithMiddleware)

	r := router.New()
	standard.InstallHandlers(r)
	config.InstallHandlers(r, databaseMiddleware)
	r.GET("/", standard.Base(), handler)

	api := prpc.Server{
		// Install an interceptor capable of reporting tsmon metrics.
		UnaryServerInterceptor: grpcmon.NewUnaryServerInterceptor(nil),
	}
	crimson.RegisterCrimsonServer(&api, rpc.NewServer())
	discovery.Enable(&api)
	api.InstallHandlers(r, databaseMiddleware)

	http.DefaultServeMux.Handle("/", r)
}

func handler(c *router.Context) {
	c.Writer.Header().Set("Content-Type", "text/plain")
	c.Writer.WriteHeader(http.StatusOK)
}
<|endoftext|>


<commit_before>/*
 * (c) 2013, Caoimhe Chaos <caoimhechaos@protonmail.com>,
 *	     Starship Factory. All rights reserved.
 *
 * Redistribution and use in source  and binary forms, with or without
 * modification, are permitted  provided that the following conditions
 * are met:
 *
 * * Redistributions of  source code  must retain the  above copyright
 *   notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this  list of conditions and the  following disclaimer in
 *   the  documentation  and/or  other  materials  provided  with  the
 *   distribution.
 * * Neither  the name  of the Starship Factory  nor the  name  of its
 *   contributors may  be used to endorse or  promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS"  AND ANY EXPRESS  OR IMPLIED WARRANTIES  OF MERCHANTABILITY
 * AND FITNESS  FOR A PARTICULAR  PURPOSE ARE DISCLAIMED. IN  NO EVENT
 * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED  TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE,  DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT  LIABILITY,  OR  TORT  (INCLUDING NEGLIGENCE  OR  OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package main

import (
	"flag"
	"html/template"
	"log"
	"net/http"
	"os"
	"time"

	"ancient-solutions.com/ancientauth"
	"ancient-solutions.com/doozer/exportedservice"
)

func main() {
	var help bool
	var bindto, template_dir string
	var lockserv, lockboot, servicename string
	var dbhost, dbname string
	var app_name, cert_file, key_file, ca_bundle, authserver, group string
	var x509keyserver string
	var result_page_size, x509_cache_size int
	var cassandra_timeout uint64
	var application_tmpl, memberlist_tmpl, print_tmpl *template.Template
	var exporter *exportedservice.ServiceExporter
	var authenticator *ancientauth.Authenticator
	var debug_authenticator bool
	var use_proxy_real_ip bool
	var db *MembershipDB
	var err error

	flag.BoolVar(&help, "help", false, "Display help")
	flag.StringVar(&bindto, "bind", "127.0.0.1:8080",
		"The address to bind the web server to")
	flag.StringVar(&lockserv, "lockserver-uri",
		os.Getenv("DOOZER_URI"),
		"URI of a Doozer cluster to connect to")
	flag.StringVar(&dbhost, "cassandra-db-host", "localhost:9160",
		"Host:port pair of the Cassandra database server")
	flag.StringVar(&dbname, "cassandra-db-name", "sfmembersys",
		"Name of the keyspace on the cassandra server to use")
	flag.StringVar(&template_dir, "template-dir", "",
		"Path to the directory with the HTML templates")
	flag.StringVar(&lockboot, "lockserver-boot-uri",
		os.Getenv("DOOZER_BOOT_URI"),
		"Boot URI to resolve the Doozer cluster name (if required)")
	flag.StringVar(&servicename, "service-name",
		"", "Service name to publish as to the lock server")
	flag.Uint64Var(&cassandra_timeout, "cassandra-timeout", 0,
		"Time (in milliseconds) to wait for a Cassandra connection, 0 means unlimited")
	flag.BoolVar(&use_proxy_real_ip, "use-proxy-real-ip", false,
		"Use the X-Real-IP header set by a proxy to determine remote addresses")
	flag.BoolVar(&debug_authenticator, "debug-authenticator", false,
		"Debug the authenticator?")

	// Behavioral flags.
	flag.IntVar(&result_page_size, "result-page-size", 25,
		"Show this many records on a result page")

	// AncientAuth flags.
	flag.StringVar(&app_name, "app-name", "Starship Factory Membership System",
		"Set the app name to this value. It may be displayed to the user when authenticating")
	flag.StringVar(&cert_file, "cert", "membersys.crt",
		"Path to the service X.509 certificate file for authenticating to the login service")
	flag.StringVar(&key_file, "key", "membersys.key",
		"Path to the key for the service certificate, in PEM encoded DER format")
	flag.StringVar(&ca_bundle, "ca-bundle", "ca.crt",
		"A bundle of X.509 certificates for authenticating the login service")
	flag.StringVar(&authserver, "login-server", "login.ancient-solutions.com",
		"DNS name of the login service to be used for authenticating users")
	flag.StringVar(&group, "desired-group", "",
		"Group an user should be a member of in order to use the admin interface")
	flag.StringVar(&x509keyserver, "x509-keyserver", "",
		"Specification of the X.509 key server to use for looking up certificates. "+
			"Leave empty to disable certificate lookups.")
	flag.IntVar(&x509_cache_size, "x509-cache-size", 4,
		"Number of certificates to be cached")
	flag.Parse()

	if help {
		flag.Usage()
		os.Exit(1)
	}

	if len(template_dir) <= 0 {
		log.Fatal("The --template-dir flag must not be empty")
	}

	// Load and parse the HTML templates to be displayed.
	application_tmpl, err = template.ParseFiles(template_dir + "/form.html")
	if err != nil {
		log.Fatal("Unable to parse form template: ", err)
	}
	application_tmpl.Funcs(fmap)

	print_tmpl, err = template.ParseFiles(template_dir + "/printlayout.html")
	if err != nil {
		log.Fatal("Unable to parse print layout template: ", err)
	}
	print_tmpl.Funcs(fmap)

	memberlist_tmpl = template.New("memberlist")
	memberlist_tmpl.Funcs(fmap)
	memberlist_tmpl, err = memberlist_tmpl.ParseFiles(template_dir + "/memberlist.html")
	if err != nil {
		log.Fatal("Unable to parse member list template: ", err)
	}
	memberlist_tmpl.Funcs(fmap)

	authenticator, err = ancientauth.NewAuthenticator(
		app_name, cert_file, key_file, ca_bundle, authserver, x509keyserver,
		x509_cache_size)
	if err != nil {
		log.Fatal("Unable to assemble authenticator: ", err)
	}

	if debug_authenticator {
		authenticator.Debug()
	}

	db, err = NewMembershipDB(dbhost, dbname, time.Duration(cassandra_timeout)*time.Millisecond)
	if err != nil {
		log.Fatal("Unable to connect to the cassandra DB ", dbname, " at ", dbhost,
			": ", err)
	}

	// Register the URL handler to be invoked.
	http.Handle("/admin/api/accept", &MemberAcceptHandler{
		admingroup: group,
		auth:       authenticator,
		database:   db,
	})

	http.Handle("/admin/api/reject", &MemberRejectHandler{
		admingroup: group,
		auth:       authenticator,
		database:   db,
	})

	http.Handle("/admin/api/agreement-upload", &MemberAgreementUploadHandler{
		admingroup: group,
		auth:       authenticator,
		database:   db,
	})

	http.Handle("/admin", &ApplicantListHandler{
		admingroup: group,
		auth:       authenticator,
		database:   db,
		pagesize:   int32(result_page_size),
		template:   memberlist_tmpl,
	})

	http.Handle("/", &FormInputHandler{
		applicationTmpl: application_tmpl,
		database:        db,
		passthrough:     http.FileServer(http.Dir(template_dir)),
		printTmpl:       print_tmpl,
		useProxyRealIP:  use_proxy_real_ip,
	})

	// If a lock server was specified, attempt to use an anonymous port as
	// a Doozer exported HTTP service. Otherwise, just bind to the address
	// given in bindto, for debugging etc.
	if len(lockserv) > 0 {
		exporter, err = exportedservice.NewExporter(lockserv, lockboot)
		if err != nil {
			log.Fatal("doozer.DialUri ", lockserv, " (",
				lockboot, "): ", err)
		}

		defer exporter.UnexportPort()
		err = exporter.ListenAndServeNamedHTTP(servicename, bindto, nil)
		if err != nil {
			log.Fatal("ListenAndServe: ", err)
		}
	} else {
		err = http.ListenAndServe(bindto, nil)
		if err != nil {
			log.Fatal("ListenAndServe: ", err)
		}
	}
}
<commit_msg>Move to a configuration file instead of death by 10'000 flags.<commit_after>/*
 * (c) 2013, Caoimhe Chaos <caoimhechaos@protonmail.com>,
 *	     Starship Factory. All rights reserved.
 *
 * Redistribution and use in source  and binary forms, with or without
 * modification, are permitted  provided that the following conditions
 * are met:
 *
 * * Redistributions of  source code  must retain the  above copyright
 *   notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this  list of conditions and the  following disclaimer in
 *   the  documentation  and/or  other  materials  provided  with  the
 *   distribution.
 * * Neither  the name  of the Starship Factory  nor the  name  of its
 *   contributors may  be used to endorse or  promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS"  AND ANY EXPRESS  OR IMPLIED WARRANTIES  OF MERCHANTABILITY
 * AND FITNESS  FOR A PARTICULAR  PURPOSE ARE DISCLAIMED. IN  NO EVENT
 * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED  TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE,  DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT  LIABILITY,  OR  TORT  (INCLUDING NEGLIGENCE  OR  OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package main

import (
	"flag"
	"html/template"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"time"

	"ancient-solutions.com/ancientauth"
	"ancient-solutions.com/doozer/exportedservice"
	"code.google.com/p/goprotobuf/proto"
)

func main() {
	var help bool
	var bindto, config_file string
	var config_contents []byte
	var application_tmpl, memberlist_tmpl, print_tmpl *template.Template
	var exporter *exportedservice.ServiceExporter
	var authenticator *ancientauth.Authenticator
	var debug_authenticator bool
	var config MembersysConfig
	var db *MembershipDB
	var err error

	flag.BoolVar(&help, "help", false, "Display help")
	flag.StringVar(&bindto, "bind", "127.0.0.1:8080",
		"The address to bind the web server to")
	flag.StringVar(&config_file, "config",
		"", "Path to a file containing a MembersysConfig protocol buffer")
	flag.BoolVar(&debug_authenticator, "debug-authenticator", false,
		"Debug the authenticator?")
	flag.Parse()

	if help || config_file == "" {
		flag.Usage()
		os.Exit(1)
	}

	config_contents, err = ioutil.ReadFile(config_file)
	if err != nil {
		log.Fatal("Unable to read ", config_file, ": ", err)
	}
	err = proto.Unmarshal(config_contents, &config)
	if err != nil {
		err = proto.UnmarshalText(string(config_contents), &config)
	}
	if err != nil {
		log.Fatal("Error parsing ", config_file, ": ", err)
	}

	// Load and parse the HTML templates to be displayed.
	application_tmpl, err = template.ParseFiles(
		config.GetTemplateDir() + "/form.html")
	if err != nil {
		log.Fatal("Unable to parse form template: ", err)
	}

	print_tmpl, err = template.ParseFiles(
		config.GetTemplateDir() + "/printlayout.html")
	if err != nil {
		log.Fatal("Unable to parse print layout template: ", err)
	}

	memberlist_tmpl = template.New("memberlist")
	memberlist_tmpl.Funcs(fmap)
	memberlist_tmpl, err = memberlist_tmpl.ParseFiles(
		config.GetTemplateDir() + "/memberlist.html")
	if err != nil {
		log.Fatal("Unable to parse member list template: ", err)
	}

	authenticator, err = ancientauth.NewAuthenticator(
		config.AuthenticationConfig.GetAppName(),
		config.AuthenticationConfig.GetCertPath(),
		config.AuthenticationConfig.GetKeyPath(),
		config.AuthenticationConfig.GetCaBundlePath(),
		config.AuthenticationConfig.GetAuthServerHost(),
		config.AuthenticationConfig.GetX509KeyserverHost(),
		int(config.AuthenticationConfig.GetX509CertificateCacheSize()))
	if err != nil {
		log.Fatal("Unable to assemble authenticator: ", err)
	}

	if debug_authenticator {
		authenticator.Debug()
	}

	db, err = NewMembershipDB(config.DatabaseConfig.GetDatabaseServer(),
		config.DatabaseConfig.GetDatabaseName(),
		time.Duration(config.DatabaseConfig.GetDatabaseTimeout())*time.Millisecond)
	if err != nil {
		log.Fatal("Unable to connect to the cassandra DB ",
			config.DatabaseConfig.GetDatabaseServer(), " at ",
			config.DatabaseConfig.GetDatabaseName(), ": ", err)
	}

	// Register the URL handler to be invoked.
	http.Handle("/admin/api/accept", &MemberAcceptHandler{
		admingroup: config.AuthenticationConfig.GetAuthGroup(),
		auth:       authenticator,
		database:   db,
	})

	http.Handle("/admin/api/reject", &MemberRejectHandler{
		admingroup: config.AuthenticationConfig.GetAuthGroup(),
		auth:       authenticator,
		database:   db,
	})

	http.Handle("/admin/api/agreement-upload", &MemberAgreementUploadHandler{
		admingroup: config.AuthenticationConfig.GetAuthGroup(),
		auth:       authenticator,
		database:   db,
	})

	http.Handle("/admin", &ApplicantListHandler{
		admingroup: config.AuthenticationConfig.GetAuthGroup(),
		auth:       authenticator,
		database:   db,
		pagesize:   config.GetResultPageSize(),
		template:   memberlist_tmpl,
	})

	http.Handle("/", &FormInputHandler{
		applicationTmpl: application_tmpl,
		database:        db,
		passthrough:     http.FileServer(http.Dir(config.GetTemplateDir())),
		printTmpl:       print_tmpl,
		useProxyRealIP:  config.GetUseProxyRealIp(),
	})

	// If a lock server was specified, attempt to use an anonymous port as
	// a Doozer exported HTTP service. Otherwise, just bind to the address
	// given in bindto, for debugging etc.
	if config.LockserviceConfig != nil {
		exporter, err = exportedservice.NewExporter(
			config.LockserviceConfig.GetLockserverUri(),
			config.LockserviceConfig.GetLockserverBootUri())
		if err != nil {
			log.Fatal("doozer.DialUri ",
				config.LockserviceConfig.GetLockserverUri(), " (",
				config.LockserviceConfig.GetLockserverBootUri(), "): ", err)
		}

		defer exporter.UnexportPort()
		err = exporter.ListenAndServeNamedHTTP(
			config.LockserviceConfig.GetDoozerServiceName(), bindto, nil)
		if err != nil {
			log.Fatal("ListenAndServe: ", err)
		}
	} else {
		err = http.ListenAndServe(bindto, nil)
		if err != nil {
			log.Fatal("ListenAndServe: ", err)
		}
	}
}
<|endoftext|>