<commit_before>package child

import (
	"context"
	"fmt"
	"net/http"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"

	cipd_api "go.chromium.org/luci/cipd/client/cipd"
	"go.chromium.org/luci/cipd/common"

	"go.skia.org/infra/autoroll/go/revision"
	"go.skia.org/infra/go/cipd"
	"go.skia.org/infra/go/skerr"
	"go.skia.org/infra/go/sklog"
	"go.skia.org/infra/go/util"
	"go.skia.org/infra/go/vfs"
)

const (
	cipdPackageUrlTmpl  = "%s/p/%s/+/%s"
	cipdBuganizerPrefix = "b/"
)

var (
	cipdDetailsRegex = regexp.MustCompile(`details(\d+)`)
)

// CIPDConfig provides configuration for CIPDChild.
type CIPDConfig struct {
	Name string `json:"name"`
	Tag  string `json:"tag"`
}

// See documentation for util.Validator interface.
func (c *CIPDConfig) Validate() error {
	if c.Name == "" {
		return skerr.Fmt("Name is required.")
	}
	if c.Tag == "" {
		return skerr.Fmt("Tag is required.")
	}
	return nil
}

// NewCIPD returns an implementation of Child which deals with a CIPD package.
// If the caller calls CIPDChild.Download, the destination must be a descendant of
// the provided workdir.
func NewCIPD(ctx context.Context, c CIPDConfig, client *http.Client, workdir string) (*CIPDChild, error) {
	if err := c.Validate(); err != nil {
		return nil, skerr.Wrap(err)
	}
	cipdClient, err := cipd.NewClient(client, workdir)
	if err != nil {
		return nil, skerr.Wrap(err)
	}
	return &CIPDChild{
		client: cipdClient,
		name:   c.Name,
		root:   workdir,
		tag:    c.Tag,
	}, nil
}

// CIPDChild is an implementation of Child which deals with a CIPD package.
type CIPDChild struct {
	client cipd.CIPDClient
	name   string
	root   string
	tag    string
}

// See documentation for Child interface.
func (c *CIPDChild) GetRevision(ctx context.Context, id string) (*revision.Revision, error) {
	instance, err := c.client.Describe(ctx, c.name, id)
	if err != nil {
		return nil, err
	}
	return CIPDInstanceToRevision(c.name, instance), nil
}

// See documentation for Child interface.
// Note: that this just finds all versions of the package between the last
// rolled version and the version currently pointed to by the configured tag; we
// can't know whether the tag we're tracking was ever actually applied to any of
// the package instances in between.
func (c *CIPDChild) Update(ctx context.Context, lastRollRev *revision.Revision) (*revision.Revision, []*revision.Revision, error) {
	head, err := c.client.ResolveVersion(ctx, c.name, c.tag)
	if err != nil {
		return nil, nil, skerr.Wrap(err)
	}
	tipRev, err := c.GetRevision(ctx, head.InstanceID)
	if err != nil {
		return nil, nil, skerr.Wrap(err)
	}
	if lastRollRev.Id == tipRev.Id {
		return tipRev, []*revision.Revision{}, nil
	}
	iter, err := c.client.ListInstances(ctx, c.name)
	if err != nil {
		return nil, nil, skerr.Wrap(err)
	}
	notRolledRevs := []*revision.Revision{}
	foundHead := false
	for {
		instances, err := iter.Next(ctx, 100)
		if err != nil {
			return nil, nil, skerr.Wrap(err)
		}
		if len(instances) == 0 {
			break
		}
		for _, instance := range instances {
			id := instance.Pin.InstanceID
			if id == head.InstanceID {
				foundHead = true
			}
			if id == lastRollRev.Id {
				return tipRev, notRolledRevs, nil
			}
			if foundHead {
				var rev *revision.Revision
				// Avoid sending another request for tipRev.
				if instance.Pin.InstanceID == head.InstanceID {
					rev = tipRev
				} else {
					rev, err = c.GetRevision(ctx, instance.Pin.InstanceID)
					if err != nil {
						return nil, nil, skerr.Wrap(err)
					}
				}
				notRolledRevs = append(notRolledRevs, rev)
			}
		}
	}
	return tipRev, notRolledRevs, nil
}

// VFS implements the Child interface.
func (c *CIPDChild) VFS(ctx context.Context, rev *revision.Revision) (vfs.FS, error) {
	fs, err := vfs.TempDir(ctx, c.root, "tmp")
	if err != nil {
		return nil, skerr.Wrap(err)
	}
	pin := common.Pin{
		PackageName: c.name,
		InstanceID:  rev.Id,
	}
	dest, err := filepath.Rel(c.root, fs.Dir())
	if err := c.client.FetchAndDeployInstance(ctx, dest, pin, 0); err != nil {
		return nil, skerr.Wrap(err)
	}
	return fs, nil
}

// SetClientForTesting sets the CIPDClient used by the CIPDChild so that it can
// be overridden for testing.
func (c *CIPDChild) SetClientForTesting(client cipd.CIPDClient) {
	c.client = client
}

type cipdDetailsLine struct {
	index int
	line  string
}

// CIPDInstanceToRevision creates a revision.Revision based on the given
// InstanceInfo.
func CIPDInstanceToRevision(name string, instance *cipd_api.InstanceDescription) *revision.Revision {
	rev := &revision.Revision{
		Id:          instance.Pin.InstanceID,
		Author:      instance.RegisteredBy,
		Display:     util.Truncate(instance.Pin.InstanceID, 12),
		Description: instance.Pin.String(),
		Timestamp:   time.Time(instance.RegisteredTs),
		URL:         fmt.Sprintf(cipdPackageUrlTmpl, cipd.ServiceUrl, name, instance.Pin.InstanceID),
	}
	detailsLines := []*cipdDetailsLine{}
	for _, tag := range instance.Tags {
		split := strings.SplitN(tag.Tag, ":", 2)
		if len(split) != 2 {
			sklog.Errorf("Invalid CIPD tag %q; expected <key>:<value>", tag.Tag)
			continue
		}
		key := split[0]
		val := split[1]
		if key == "bug" {
			// For bugs, we expect either eg. "chromium:1234" or "b/1234".
			split := strings.SplitN(val, ":", 2)
			if rev.Bugs == nil {
				rev.Bugs = map[string][]string{}
			}
			if len(split) == 2 {
				rev.Bugs[split[0]] = append(rev.Bugs[split[0]], split[1])
			} else if strings.HasPrefix(val, cipdBuganizerPrefix) {
				rev.Bugs[util.BUG_PROJECT_BUGANIZER] = append(rev.Bugs[util.BUG_PROJECT_BUGANIZER], val[len(cipdBuganizerPrefix):])
			} else {
				sklog.Errorf("Invalid format for \"bug\" tag: %s", tag.Tag)
			}
		} else if m := cipdDetailsRegex.FindStringSubmatch(key); len(m) == 2 {
			// For details, the tag value becomes one line. The tag key includes
			// an int which is used to determine the ordering of the lines.
			index, err := strconv.Atoi(m[1])
			if err != nil {
				// This shouldn't happen thanks to the regex.
				sklog.Errorf("Failed to parse int from details tag %q: %s", tag.Tag, err)
				continue
			}
			detailsLines = append(detailsLines, &cipdDetailsLine{
				index: index,
				line:  val,
			})
		}
	}
	// Concatenate the details lines.
	if len(detailsLines) > 0 {
		sort.Slice(detailsLines, func(i, j int) bool {
			if detailsLines[i].index == detailsLines[j].index {
				return detailsLines[i].line < detailsLines[j].line
			}
			return detailsLines[i].index < detailsLines[j].index
		})
		for idx, line := range detailsLines {
			rev.Details += line.line
			if idx < len(detailsLines)-1 {
				rev.Details += "\n"
			}
		}

	}
	return rev
}

var _ Child = &CIPDChild{}
<commit_msg>[autoroll] Fix for CIPD when the current revision is not found<commit_after>package child

import (
	"context"
	"fmt"
	"net/http"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"

	cipd_api "go.chromium.org/luci/cipd/client/cipd"
	"go.chromium.org/luci/cipd/common"

	"go.skia.org/infra/autoroll/go/revision"
	"go.skia.org/infra/go/cipd"
	"go.skia.org/infra/go/skerr"
	"go.skia.org/infra/go/sklog"
	"go.skia.org/infra/go/util"
	"go.skia.org/infra/go/vfs"
)

const (
	cipdPackageUrlTmpl  = "%s/p/%s/+/%s"
	cipdBuganizerPrefix = "b/"
)

var (
	cipdDetailsRegex = regexp.MustCompile(`details(\d+)`)
)

// CIPDConfig provides configuration for CIPDChild.
type CIPDConfig struct {
	Name string `json:"name"`
	Tag  string `json:"tag"`
}

// See documentation for util.Validator interface.
func (c *CIPDConfig) Validate() error {
	if c.Name == "" {
		return skerr.Fmt("Name is required.")
	}
	if c.Tag == "" {
		return skerr.Fmt("Tag is required.")
	}
	return nil
}

// NewCIPD returns an implementation of Child which deals with a CIPD package.
// If the caller calls CIPDChild.Download, the destination must be a descendant of
// the provided workdir.
func NewCIPD(ctx context.Context, c CIPDConfig, client *http.Client, workdir string) (*CIPDChild, error) {
	if err := c.Validate(); err != nil {
		return nil, skerr.Wrap(err)
	}
	cipdClient, err := cipd.NewClient(client, workdir)
	if err != nil {
		return nil, skerr.Wrap(err)
	}
	return &CIPDChild{
		client: cipdClient,
		name:   c.Name,
		root:   workdir,
		tag:    c.Tag,
	}, nil
}

// CIPDChild is an implementation of Child which deals with a CIPD package.
type CIPDChild struct {
	client cipd.CIPDClient
	name   string
	root   string
	tag    string
}

// See documentation for Child interface.
func (c *CIPDChild) GetRevision(ctx context.Context, id string) (*revision.Revision, error) {
	instance, err := c.client.Describe(ctx, c.name, id)
	if err != nil {
		return nil, err
	}
	return CIPDInstanceToRevision(c.name, instance), nil
}

// See documentation for Child interface.
// Note: that this just finds all versions of the package between the last
// rolled version and the version currently pointed to by the configured tag; we
// can't know whether the tag we're tracking was ever actually applied to any of
// the package instances in between.
func (c *CIPDChild) Update(ctx context.Context, lastRollRev *revision.Revision) (*revision.Revision, []*revision.Revision, error) {
	head, err := c.client.ResolveVersion(ctx, c.name, c.tag)
	if err != nil {
		return nil, nil, skerr.Wrap(err)
	}
	tipRev, err := c.GetRevision(ctx, head.InstanceID)
	if err != nil {
		return nil, nil, skerr.Wrap(err)
	}
	if lastRollRev.Id == tipRev.Id {
		return tipRev, []*revision.Revision{}, nil
	}
	iter, err := c.client.ListInstances(ctx, c.name)
	if err != nil {
		return nil, nil, skerr.Wrap(err)
	}
	notRolledRevs := []*revision.Revision{}
	foundTipRev := false
	for {
		instances, err := iter.Next(ctx, 100)
		if err != nil {
			return nil, nil, skerr.Wrap(err)
		}
		if len(instances) == 0 {
			break
		}
		for _, instance := range instances {
			id := instance.Pin.InstanceID
			if id == head.InstanceID {
				foundTipRev = true
			}
			if id == lastRollRev.Id {
				// Once we've reached lastRollRev, stop iterating.
				if !foundTipRev {
					// If we haven't found the tip rev, then the last-rolled rev
					// is actually ahead of the tip rev, which is possible when
					// tracking a ref other than "latest".  In this case, return
					// the tip rev as the single not-rolled rev.  This may cause
					// us to roll backward, so we log a warning.
					sklog.Warningf("Tip rev %q is behind last-rolled rev %q; "+
						"this may occur as a result of a manual roll when "+
						"tracking something other than \"latest\". This will "+
						"likely result in a backward roll!", tipRev.Id, lastRollRev.Id)
					return tipRev, []*revision.Revision{tipRev}, nil
				}
				return tipRev, notRolledRevs, nil
			}
			if foundTipRev {
				var rev *revision.Revision
				// Avoid sending another request for tipRev.
				if instance.Pin.InstanceID == head.InstanceID {
					rev = tipRev
				} else {
					rev, err = c.GetRevision(ctx, instance.Pin.InstanceID)
					if err != nil {
						return nil, nil, skerr.Wrap(err)
					}
				}
				notRolledRevs = append(notRolledRevs, rev)
			}
		}
	}
	return tipRev, notRolledRevs, nil
}

// VFS implements the Child interface.
func (c *CIPDChild) VFS(ctx context.Context, rev *revision.Revision) (vfs.FS, error) {
	fs, err := vfs.TempDir(ctx, c.root, "tmp")
	if err != nil {
		return nil, skerr.Wrap(err)
	}
	pin := common.Pin{
		PackageName: c.name,
		InstanceID:  rev.Id,
	}
	dest, err := filepath.Rel(c.root, fs.Dir())
	if err := c.client.FetchAndDeployInstance(ctx, dest, pin, 0); err != nil {
		return nil, skerr.Wrap(err)
	}
	return fs, nil
}

// SetClientForTesting sets the CIPDClient used by the CIPDChild so that it can
// be overridden for testing.
func (c *CIPDChild) SetClientForTesting(client cipd.CIPDClient) {
	c.client = client
}

type cipdDetailsLine struct {
	index int
	line  string
}

// CIPDInstanceToRevision creates a revision.Revision based on the given
// InstanceInfo.
func CIPDInstanceToRevision(name string, instance *cipd_api.InstanceDescription) *revision.Revision {
	rev := &revision.Revision{
		Id:          instance.Pin.InstanceID,
		Author:      instance.RegisteredBy,
		Display:     util.Truncate(instance.Pin.InstanceID, 12),
		Description: instance.Pin.String(),
		Timestamp:   time.Time(instance.RegisteredTs),
		URL:         fmt.Sprintf(cipdPackageUrlTmpl, cipd.ServiceUrl, name, instance.Pin.InstanceID),
	}
	detailsLines := []*cipdDetailsLine{}
	for _, tag := range instance.Tags {
		split := strings.SplitN(tag.Tag, ":", 2)
		if len(split) != 2 {
			sklog.Errorf("Invalid CIPD tag %q; expected <key>:<value>", tag.Tag)
			continue
		}
		key := split[0]
		val := split[1]
		if key == "bug" {
			// For bugs, we expect either eg. "chromium:1234" or "b/1234".
			split := strings.SplitN(val, ":", 2)
			if rev.Bugs == nil {
				rev.Bugs = map[string][]string{}
			}
			if len(split) == 2 {
				rev.Bugs[split[0]] = append(rev.Bugs[split[0]], split[1])
			} else if strings.HasPrefix(val, cipdBuganizerPrefix) {
				rev.Bugs[util.BUG_PROJECT_BUGANIZER] = append(rev.Bugs[util.BUG_PROJECT_BUGANIZER], val[len(cipdBuganizerPrefix):])
			} else {
				sklog.Errorf("Invalid format for \"bug\" tag: %s", tag.Tag)
			}
		} else if m := cipdDetailsRegex.FindStringSubmatch(key); len(m) == 2 {
			// For details, the tag value becomes one line. The tag key includes
			// an int which is used to determine the ordering of the lines.
			index, err := strconv.Atoi(m[1])
			if err != nil {
				// This shouldn't happen thanks to the regex.
				sklog.Errorf("Failed to parse int from details tag %q: %s", tag.Tag, err)
				continue
			}
			detailsLines = append(detailsLines, &cipdDetailsLine{
				index: index,
				line:  val,
			})
		}
	}
	// Concatenate the details lines.
	if len(detailsLines) > 0 {
		sort.Slice(detailsLines, func(i, j int) bool {
			if detailsLines[i].index == detailsLines[j].index {
				return detailsLines[i].line < detailsLines[j].line
			}
			return detailsLines[i].index < detailsLines[j].index
		})
		for idx, line := range detailsLines {
			rev.Details += line.line
			if idx < len(detailsLines)-1 {
				rev.Details += "\n"
			}
		}

	}
	return rev
}

var _ Child = &CIPDChild{}
<|endoftext|>


<commit_before>//
// Copyright © 2011-2017 Guy M. Allard
//
// 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 stompngo

import (
	"fmt"
	"strconv"
	"strings"
	"time"
)

/*
	Initialize heart beats if necessary and possible.

	Return an error, possibly nil, to mainline if initialization can not
	complete.  Establish heartbeat send and receive goroutines as necessary.
*/
func (c *Connection) initializeHeartBeats(ch Headers) (e error) {
	// Client wants Heartbeats ?
	vc, ok := ch.Contains(HK_HEART_BEAT)
	if !ok || vc == "0,0" {
		return nil
	}
	// Server wants Heartbeats ?
	vs, ok := c.ConnectResponse.Headers.Contains(HK_HEART_BEAT)
	if !ok || vs == "0,0" {
		return nil
	}
	// Work area, may or may not become connection heartbeat data
	w := &heartBeatData{cx: 0, cy: 0, sx: 0, sy: 0,
		hbs: true, hbr: true, // possible reset later
		sti: 0, rti: 0,
		ls: 0, lr: 0}

	// Client specified values
	cp := strings.Split(vc, ",")
	if len(cp) != 2 { // S/B caught by the server first
		return Error("invalid client heart-beat header: " + vc)
	}
	w.cx, e = strconv.ParseInt(cp[0], 10, 64)
	if e != nil {
		return Error("non-numeric cx heartbeat value: " + cp[0])
	}
	w.cy, e = strconv.ParseInt(cp[1], 10, 64)
	if e != nil {
		return Error("non-numeric cy heartbeat value: " + cp[1])
	}

	// Server specified values
	sp := strings.Split(vs, ",")
	if len(sp) != 2 {
		return Error("invalid server heart-beat header: " + vs)
	}
	w.sx, e = strconv.ParseInt(sp[0], 10, 64)
	if e != nil {
		return Error("non-numeric sx heartbeat value: " + sp[0])
	}
	w.sy, e = strconv.ParseInt(sp[1], 10, 64)
	if e != nil {
		return Error("non-numeric sy heartbeat value: " + sp[1])
	}

	// Check for sending needed
	if w.cx == 0 || w.sy == 0 {
		w.hbs = false //
	}

	// Check for receiving needed
	if w.sx == 0 || w.cy == 0 {
		w.hbr = false //
	}

	// ========================================================================

	if !w.hbs && !w.hbr {
		return nil // none required
	}

	// ========================================================================

	c.hbd = w                   // OK, we are doing some kind of heartbeating
	ct := time.Now().UnixNano() // Prime current time

	if w.hbs { // Finish sender parameters if required
		sm := max(w.cx, w.sy)       // ticker interval, ms
		w.sti = 1000000 * sm        // ticker interval, ns
		w.ssd = make(chan struct{}) // add shutdown channel
		w.ls = ct                   // Best guess at start
		// fmt.Println("start send ticker")
		go c.sendTicker()
	}

	if w.hbr { // Finish receiver parameters if required
		rm := max(w.sx, w.cy)       // ticker interval, ms
		w.rti = 1000000 * rm        // ticker interval, ns
		w.rsd = make(chan struct{}) // add shutdown channel
		w.lr = ct                   // Best guess at start
		// fmt.Println("start receive ticker")
		go c.receiveTicker()
	}
	return nil
}

/*
	The heart beat send ticker.
*/
func (c *Connection) sendTicker() {
	c.hbd.sc = 0
	ticker := time.NewTicker(time.Duration(c.hbd.sti))
hbSend:
	for {
		select {
		case <-ticker.C:
			c.log("HeartBeat Send data")
			// Send a heartbeat
			f := Frame{"\n", Headers{}, NULLBUFF} // Heartbeat frame
			r := make(chan error)
			c.output <- wiredata{f, r}
			e := <-r
			//
			c.hbd.sdl.Lock()
			if e != nil {
				fmt.Printf("Heartbeat Send Failure: %v\n", e)
				c.Hbsf = true
			} else {
				c.Hbsf = false
				c.hbd.sc++
			}
			c.hbd.sdl.Unlock()
			//
		case _ = <-c.hbd.ssd:
			break hbSend
		case _ = <-c.ssdc:
			break hbSend
		} // End of select
	} // End of for
	c.log("Heartbeat Send Ends", time.Now())
	return
}

/*
	The heart beat receive ticker.
*/
func (c *Connection) receiveTicker() {
	c.hbd.rc = 0
	var first, last, nd int64
hbGet:
	for {
		nd = c.hbd.rti - (last - first)
		// Check if receives are supposed to be "fast" *and* we spent a
		// lot of time in the previous loop.
		if nd <= 0 {
			nd = c.hbd.rti
		}
		ticker := time.NewTicker(time.Duration(nd))
		select {
		case ct := <-ticker.C:
			first = time.Now().UnixNano()
			ticker.Stop()
			c.hbd.rdl.Lock()
			flr := c.hbd.lr
			ld := ct.UnixNano() - flr
			c.log("HeartBeat Receive TIC", "TickerVal", ct.UnixNano(),
				"LastReceive", flr, "Diff", ld)
			if ld > (c.hbd.rti + (c.hbd.rti / 5)) { // swag plus to be tolerant
				c.log("HeartBeat Receive Read is dirty")
				c.Hbrf = true // Flag possible dirty connection
			} else {
				c.Hbrf = false // Reset
				c.hbd.rc++
			}
			c.hbd.rdl.Unlock()
			last = time.Now().UnixNano()
		case _ = <-c.hbd.rsd:
			break hbGet
		case _ = <-c.ssdc:
			break hbGet
		} // End of select
	} // End of for
	c.log("Heartbeat Receive Ends", time.Now())
	return
}
<commit_msg>bugfix: ticker overflow<commit_after>//
// Copyright © 2011-2017 Guy M. Allard
//
// 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 stompngo

import (
	"fmt"
	"strconv"
	"strings"
	"time"
)

/*
	Initialize heart beats if necessary and possible.

	Return an error, possibly nil, to mainline if initialization can not
	complete.  Establish heartbeat send and receive goroutines as necessary.
*/
func (c *Connection) initializeHeartBeats(ch Headers) (e error) {
	// Client wants Heartbeats ?
	vc, ok := ch.Contains(HK_HEART_BEAT)
	if !ok || vc == "0,0" {
		return nil
	}
	// Server wants Heartbeats ?
	vs, ok := c.ConnectResponse.Headers.Contains(HK_HEART_BEAT)
	if !ok || vs == "0,0" {
		return nil
	}
	// Work area, may or may not become connection heartbeat data
	w := &heartBeatData{cx: 0, cy: 0, sx: 0, sy: 0,
		hbs: true, hbr: true, // possible reset later
		sti: 0, rti: 0,
		ls: 0, lr: 0}

	// Client specified values
	cp := strings.Split(vc, ",")
	if len(cp) != 2 { // S/B caught by the server first
		return Error("invalid client heart-beat header: " + vc)
	}
	w.cx, e = strconv.ParseInt(cp[0], 10, 64)
	if e != nil {
		return Error("non-numeric cx heartbeat value: " + cp[0])
	}
	w.cy, e = strconv.ParseInt(cp[1], 10, 64)
	if e != nil {
		return Error("non-numeric cy heartbeat value: " + cp[1])
	}

	// Server specified values
	sp := strings.Split(vs, ",")
	if len(sp) != 2 {
		return Error("invalid server heart-beat header: " + vs)
	}
	w.sx, e = strconv.ParseInt(sp[0], 10, 64)
	if e != nil {
		return Error("non-numeric sx heartbeat value: " + sp[0])
	}
	w.sy, e = strconv.ParseInt(sp[1], 10, 64)
	if e != nil {
		return Error("non-numeric sy heartbeat value: " + sp[1])
	}

	// Check for sending needed
	if w.cx == 0 || w.sy == 0 {
		w.hbs = false //
	}

	// Check for receiving needed
	if w.sx == 0 || w.cy == 0 {
		w.hbr = false //
	}

	// ========================================================================

	if !w.hbs && !w.hbr {
		return nil // none required
	}

	// ========================================================================

	c.hbd = w                   // OK, we are doing some kind of heartbeating
	ct := time.Now().UnixNano() // Prime current time

	if w.hbs { // Finish sender parameters if required
		sm := max(w.cx, w.sy)       // ticker interval, ms
		w.sti = 1000000 * sm        // ticker interval, ns
		w.ssd = make(chan struct{}) // add shutdown channel
		w.ls = ct                   // Best guess at start
		// fmt.Println("start send ticker")
		go c.sendTicker()
	}

	if w.hbr { // Finish receiver parameters if required
		rm := max(w.sx, w.cy)       // ticker interval, ms
		w.rti = 1000000 * rm        // ticker interval, ns
		w.rsd = make(chan struct{}) // add shutdown channel
		w.lr = ct                   // Best guess at start
		// fmt.Println("start receive ticker")
		go c.receiveTicker()
	}
	return nil
}

/*
	The heart beat send ticker.
*/
func (c *Connection) sendTicker() {
	c.hbd.sc = 0
	ticker := time.NewTicker(time.Duration(c.hbd.sti))
	defer ticker.Stop()
hbSend:
	for {
		select {
		case <-ticker.C:
			c.log("HeartBeat Send data")
			// Send a heartbeat
			f := Frame{"\n", Headers{}, NULLBUFF} // Heartbeat frame
			r := make(chan error)
			c.output <- wiredata{f, r}
			e := <-r
			//
			c.hbd.sdl.Lock()
			if e != nil {
				fmt.Printf("Heartbeat Send Failure: %v\n", e)
				c.Hbsf = true
			} else {
				c.Hbsf = false
				c.hbd.sc++
			}
			c.hbd.sdl.Unlock()
			//
		case _ = <-c.hbd.ssd:
			break hbSend
		case _ = <-c.ssdc:
			break hbSend
		} // End of select
	} // End of for
	c.log("Heartbeat Send Ends", time.Now())
	return
}

/*
	The heart beat receive ticker.
*/
func (c *Connection) receiveTicker() {
	c.hbd.rc = 0
	var first, last, nd int64
hbGet:
	for {
		nd = c.hbd.rti - (last - first)
		// Check if receives are supposed to be "fast" *and* we spent a
		// lot of time in the previous loop.
		if nd <= 0 {
			nd = c.hbd.rti
		}
		ticker := time.NewTicker(time.Duration(nd))
		select {
		case ct := <-ticker.C:
			first = time.Now().UnixNano()
			ticker.Stop()
			c.hbd.rdl.Lock()
			flr := c.hbd.lr
			ld := ct.UnixNano() - flr
			c.log("HeartBeat Receive TIC", "TickerVal", ct.UnixNano(),
				"LastReceive", flr, "Diff", ld)
			if ld > (c.hbd.rti + (c.hbd.rti / 5)) { // swag plus to be tolerant
				c.log("HeartBeat Receive Read is dirty")
				c.Hbrf = true // Flag possible dirty connection
			} else {
				c.Hbrf = false // Reset
				c.hbd.rc++
			}
			c.hbd.rdl.Unlock()
			last = time.Now().UnixNano()
		case _ = <-c.hbd.rsd:
			ticker.Stop()
			break hbGet
		case _ = <-c.ssdc:
			ticker.Stop()
			break hbGet
		} // End of select
	} // End of for
	c.log("Heartbeat Receive Ends", time.Now())
	return
}
<|endoftext|>


<commit_before>package doggy

import (
	"log"
	"os"
	"path/filepath"
	"time"

	"github.com/go-ini/ini"
	"github.com/uber-go/zap"
)

type Config struct {
	Listen     string           `ini:"listen"`
	Env        string           `ini:"env"`
	Logger     LoggerConfig     `ini:"log"`
	Middleware MiddlewareConfig `ini:"middleware"`
}

type LoggerConfig struct {
	File  *os.File  `ini:"-"`
	Level zap.Level `ini:"level"`
	Dir   string    `ini:"dir"`
}

type MiddlewareConfig struct {
	Timeout time.Duration `ini:"timeout"`
}

var configPath = "config.ini"

func SetConfigPath(path string) {
	configPath = path
}

var config Config

func LoadConfig(v, source interface{}, others ...interface{}) error {
	err := ini.MapTo(v, source, others...)
	if err != nil {
		return err
	}
	return nil
}

func init() {
	config = Config{
		Listen: "0.0.0.0:8000",
		Env:    "dev",
		Logger: LoggerConfig{
			Level: zap.DebugLevel,
			File:  os.Stdout,
		},
		Middleware: MiddlewareConfig{
			Timeout: 5 * time.Second,
		},
	}

	LoadConfig(&config, configPath)

	if config.Env == "prod" {
		config.Logger.Level = zap.ErrorLevel
	}

	if len(config.Logger.Dir) != 0 {
		name, err := filepath.Abs(config.Logger.Dir)
		if err != nil {
			log.Panic("os.OpenFile failed", err)
		}
		l, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600)
		if err != nil {
			log.Panic("os.OpenFile failed", err)
		}
		config.Logger.File = l
	}
}
<commit_msg>check error<commit_after>package doggy

import (
	"log"
	"os"
	"path/filepath"
	"time"

	"github.com/go-ini/ini"
	"github.com/uber-go/zap"
)

type Config struct {
	Listen     string           `ini:"listen"`
	Env        string           `ini:"env"`
	Logger     LoggerConfig     `ini:"log"`
	Middleware MiddlewareConfig `ini:"middleware"`
}

type LoggerConfig struct {
	File  *os.File  `ini:"-"`
	Level zap.Level `ini:"level"`
	Dir   string    `ini:"dir"`
}

type MiddlewareConfig struct {
	Timeout time.Duration `ini:"timeout"`
}

var configPath = "config.ini"

func SetConfigPath(path string) {
	configPath = path
}

var config Config

func LoadConfig(v, source interface{}, others ...interface{}) error {
	err := ini.MapTo(v, source, others...)
	if err != nil {
		return err
	}
	return nil
}

func init() {
	config = Config{
		Listen: "0.0.0.0:8000",
		Env:    "dev",
		Logger: LoggerConfig{
			Level: zap.DebugLevel,
			File:  os.Stdout,
		},
		Middleware: MiddlewareConfig{
			Timeout: 5 * time.Second,
		},
	}

	if err := LoadConfig(&config, configPath); err != nil {
		log.Panic("LoadConfig failed", err)
	}

	if config.Env == "prod" {
		config.Logger.Level = zap.ErrorLevel
	}

	if len(config.Logger.Dir) != 0 {
		name, err := filepath.Abs(config.Logger.Dir)
		if err != nil {
			log.Panic("os.OpenFile failed", err)
		}
		l, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600)
		if err != nil {
			log.Panic("os.OpenFile failed", err)
		}
		config.Logger.File = l
	}
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"net/url"
	"os"
	"regexp"
	"runtime"
	"strconv"
	"strings"
)

type Config struct {
	requests    int
	concurrency int
	timelimit   int

	method              string
	bodyFile            []byte
	contentType         string
	headers             []string
	cookies             []string
	gzip                bool
	keepAlive           bool
	basicAuthentication string
	userAgent           string

	url  string
	host string
	port int

	serverName  string
	contentSize int
}

func loadConfig() (config *Config, err error) {
	// setup command-line flags
	flag.IntVar(&Verbosity, "v", 0, "How much troubleshooting info to print")
	flag.IntVar(&GoMaxProcs, "G", runtime.NumCPU(), "Number of Goroutine procs")
	flag.BoolVar(&ContinueOnError, "r", false, "Don't exit on socket receive errors")

	request := flag.Int("n", 1, "Number of requests to perform")
	concurrency := flag.Int("c", 1, "Number of multiple requests to make")
	timelimit := flag.Int("t", 0, "Seconds to max. wait for responses")

	postFile := flag.String("p", "", "File containing data to POST. Remember also to set -T")
	putFile := flag.String("u", "", "File containing data to PUT. Remember also to set -T")
	contentType := flag.String("T", "text/plain", "Content-type header for POSTing, eg. 'application/x-www-form-urlencoded' Default is 'text/plain'")

	var headers, cookies stringSet
	flag.Var(&headers, "H", "Add Arbitrary header line, eg. 'Accept-Encoding: gzip' Inserted after all normal header lines. (repeatable)")
	flag.Var(&cookies, "C", "Add cookie, eg. 'Apache=1234. (repeatable)")

	basicAuthentication := flag.String("A", "", "Add Basic WWW Authentication, the attributes are a colon separated username and password.")
	keepAlive := flag.Bool("k", false, "Use HTTP KeepAlive feature")
	gzip := flag.Bool("z", false, "Use HTTP Gzip feature")

	help := flag.Bool("h", false, "Display usage information (this message)")

	flag.Usage = func() {
		fmt.Print("Usage: gb [options] http[s]://hostname[:port]/path\nOptions are:\n")
		flag.PrintDefaults()
	}

	urlStr := os.Args[len(os.Args)-1]
	isUrl, _ := regexp.MatchString(`http.*?://.*`, urlStr)

	flag.Parse()

	if *help || len(os.Args) == 1 || !isUrl {
		flag.Usage()
		os.Exit(0)
	}

	// build configuration
	config = &Config{}
	config.requests = *request
	config.concurrency = *concurrency

	filename := ""
	if *postFile == "" && *putFile == "" {
		config.method = "GET"
	} else if *postFile != "" {
		config.method = "POST"
		filename = *postFile
	} else if *putFile != "PUT" {
		config.method = "PUT"
		filename = *putFile
	}

	if filename != "" {
		if err = loadFile(config, filename); err != nil {
			return
		}
	}

	if *timelimit > 0 {
		config.timelimit = *timelimit
		if config.requests == 1 {
			config.requests = MAX_REQUESTS
		}
	}

	config.contentType = *contentType
	config.keepAlive = *keepAlive
	config.gzip = *gzip
	config.basicAuthentication = *basicAuthentication

	URL, err := url.Parse(urlStr)
	if err != nil {
		return
	}
	config.host, config.port = extractHostAndPort(URL)
	config.url = urlStr

	if Verbosity > 1 {
		fmt.Printf("dump config: %#+v\n", config)
	}

	// validate configuration
	if config.requests < 1 || config.concurrency < 1 || config.timelimit < 0 || GoMaxProcs < 1 || Verbosity < 0 {
		err = errors.New("wrong number of arguments")
		return
	}

	if config.concurrency > config.requests {
		err = errors.New("Cannot use concurrency level greater than total number of requests")
		return
	}

	return

}

func loadFile(config *Config, filename string) error {
	bytes, err := ioutil.ReadFile(filename)
	if err != nil {
		return err
	} else {
		config.bodyFile = bytes
	}
	return nil
}

type stringSet []string

func (f *stringSet) String() string {
	return fmt.Sprint([]string(*f))
}

func (f *stringSet) Set(value string) error {
	*f = append(*f, value)
	return nil
}

func extractHostAndPort(url *url.URL) (host string, port int) {

	hostname := url.Host
	pos := strings.LastIndex(hostname, ":")
	if pos > 0 {
		portInt64, _ := strconv.Atoi(hostname[pos+1:])
		host = hostname[0:pos]
		port = int(portInt64)
	} else {
		host = hostname
		if url.Scheme == "http" {
			port = 80
		} else if url.Scheme == "https" {
			port = 443
		} else {
			panic("unsupported protocol schema:" + url.Scheme)
		}
	}

	return
}
<commit_msg>add head method feature<commit_after>package main

import (
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"net/url"
	"os"
	"regexp"
	"runtime"
	"strconv"
	"strings"
)

type Config struct {
	requests    int
	concurrency int
	timelimit   int

	method              string
	bodyFile            []byte
	contentType         string
	headers             []string
	cookies             []string
	gzip                bool
	keepAlive           bool
	basicAuthentication string
	userAgent           string

	url  string
	host string
	port int

	serverName  string
	contentSize int
}

func loadConfig() (config *Config, err error) {
	// setup command-line flags
	flag.IntVar(&Verbosity, "v", 0, "How much troubleshooting info to print")
	flag.IntVar(&GoMaxProcs, "G", runtime.NumCPU(), "Number of Goroutine procs")
	flag.BoolVar(&ContinueOnError, "r", false, "Don't exit on socket receive errors")

	request := flag.Int("n", 1, "Number of requests to perform")
	concurrency := flag.Int("c", 1, "Number of multiple requests to make")
	timelimit := flag.Int("t", 0, "Seconds to max. wait for responses")

	postFile := flag.String("p", "", "File containing data to POST. Remember also to set -T")
	putFile := flag.String("u", "", "File containing data to PUT. Remember also to set -T")
	headMethod := flag.Bool("i", false, "Use HEAD instead of GET")
	contentType := flag.String("T", "text/plain", "Content-type header for POSTing, eg. 'application/x-www-form-urlencoded' Default is 'text/plain'")

	var headers, cookies stringSet
	flag.Var(&headers, "H", "Add Arbitrary header line, eg. 'Accept-Encoding: gzip' Inserted after all normal header lines. (repeatable)")
	flag.Var(&cookies, "C", "Add cookie, eg. 'Apache=1234. (repeatable)")

	basicAuthentication := flag.String("A", "", "Add Basic WWW Authentication, the attributes are a colon separated username and password.")
	keepAlive := flag.Bool("k", false, "Use HTTP KeepAlive feature")
	gzip := flag.Bool("z", false, "Use HTTP Gzip feature")

	help := flag.Bool("h", false, "Display usage information (this message)")

	flag.Usage = func() {
		fmt.Print("Usage: gb [options] http[s]://hostname[:port]/path\nOptions are:\n")
		flag.PrintDefaults()
	}

	urlStr := os.Args[len(os.Args)-1]
	isUrl, _ := regexp.MatchString(`http.*?://.*`, urlStr)

	flag.Parse()

	if *help || len(os.Args) == 1 || !isUrl {
		flag.Usage()
		os.Exit(0)
	}

	// build configuration
	config = &Config{}
	config.requests = *request
	config.concurrency = *concurrency

	switch {
	case *postFile == "POST":
		config.method = "POST"
		if err = loadFile(config, *postFile); err != nil {
			return
		}
	case *putFile == "PUT":
		config.method = "PUT"
		if err = loadFile(config, *putFile); err != nil {
			return
		}
	case *headMethod:
		config.method = "HEAD"
	default:
		config.method = "GET"
	}

	if *timelimit > 0 {
		config.timelimit = *timelimit
		if config.requests == 1 {
			config.requests = MAX_REQUESTS
		}
	}

	config.contentType = *contentType
	config.keepAlive = *keepAlive
	config.gzip = *gzip
	config.basicAuthentication = *basicAuthentication

	URL, err := url.Parse(urlStr)
	if err != nil {
		return
	}
	config.host, config.port = extractHostAndPort(URL)
	config.url = urlStr

	if Verbosity > 1 {
		fmt.Printf("dump config: %#+v\n", config)
	}

	// validate configuration
	if config.requests < 1 || config.concurrency < 1 || config.timelimit < 0 || GoMaxProcs < 1 || Verbosity < 0 {
		err = errors.New("wrong number of arguments")
		return
	}

	if config.concurrency > config.requests {
		err = errors.New("Cannot use concurrency level greater than total number of requests")
		return
	}

	return

}

func loadFile(config *Config, filename string) error {
	bytes, err := ioutil.ReadFile(filename)
	if err != nil {
		return err
	} else {
		config.bodyFile = bytes
	}
	return nil
}

type stringSet []string

func (f *stringSet) String() string {
	return fmt.Sprint([]string(*f))
}

func (f *stringSet) Set(value string) error {
	*f = append(*f, value)
	return nil
}

func extractHostAndPort(url *url.URL) (host string, port int) {

	hostname := url.Host
	pos := strings.LastIndex(hostname, ":")
	if pos > 0 {
		portInt64, _ := strconv.Atoi(hostname[pos+1:])
		host = hostname[0:pos]
		port = int(portInt64)
	} else {
		host = hostname
		if url.Scheme == "http" {
			port = 80
		} else if url.Scheme == "https" {
			port = 443
		} else {
			panic("unsupported protocol schema:" + url.Scheme)
		}
	}

	return
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/michaelsauter/crane/print"
	"gopkg.in/v1/yaml"
	"io/ioutil"
	"os"
	"path/filepath"
)

type Manifest struct {
	Containers Containers          `json:"containers" yaml:"containers"`
	Groups     map[string][]string `json:"groups" yaml:"groups"`
}

func determineTargetedContainers(manifest Manifest, specifiedGroup string) []string {
	// If group is not given, all containers
	if len(specifiedGroup) == 0 {
		var containers []string
		for i := 0; i < len(manifest.Containers); i++ {
			containers = append(containers, manifest.Containers[i].Name)
		}
		return containers
	}
	// Select specified group from listed groups
	for name, containers := range manifest.Groups {
		if name == specifiedGroup {
			return containers
		}
	}
	// Otherwise, the group is just the specified container
	return append([]string{}, specifiedGroup)
}

func getManifest(options Options) Manifest {
	if len(options.config) > 0 {
		return unmarshalJSON([]byte(options.config))
	} else {
		for _, f := range manifestFiles() {
			if _, err := os.Stat(f); err == nil {
				return readCraneData(f)
			}
		}
	}
	panic(StatusError{fmt.Errorf("No configuration found %v", manifestFiles()), 78})
}

func getContainers(options Options) Containers {
	manifest := getManifest(options)
	targetedContainers := determineTargetedContainers(manifest, options.group)
	return manifest.Containers.filter(targetedContainers)
}

func readCraneData(filename string) Manifest {
	data, err := ioutil.ReadFile(filename)
	if err != nil {
		panic(err)
	}

	if filename == "Cranefile" {
		print.Notice("Using a Cranefile is deprecated. Please use crane.json/crane.yaml instead.\n")
	}

	ext := filepath.Ext(filename)
	if ext == ".json" {
		return unmarshalJSON(data)
	} else if ext == ".yml" || ext == ".yaml" {
		return unmarshalYAML(data)
	} else if ext == "" {
		return unmarshalJSON(data)
	} else {
		panic("Unrecognized file extension")
	}
}

// Thanks to https://github.com/markpeek/packer/commit/5bf33a0e91b2318a40c42e9bf855dcc8dd4cdec5
func displaySyntaxError(data []byte, syntaxError error) (err error) {
	syntax, ok := syntaxError.(*json.SyntaxError)
	if !ok {
		err = syntaxError
		return
	}
	newline := []byte{'\x0a'}
	space := []byte{' '}

	start, end := bytes.LastIndex(data[:syntax.Offset], newline)+1, len(data)
	if idx := bytes.Index(data[start:], newline); idx >= 0 {
		end = start + idx
	}

	line, pos := bytes.Count(data[:start], newline)+1, int(syntax.Offset)-start-1

	err = fmt.Errorf("\nError in line %d: %s \n%s\n%s^", line, syntaxError, data[start:end], bytes.Repeat(space, pos))
	return
}

func unmarshalJSON(data []byte) Manifest {
	var manifest Manifest
	err := json.Unmarshal(data, &manifest)
	if err != nil {
		err = displaySyntaxError(data, err)
		panic(err)
	}
	return manifest
}

func unmarshalYAML(data []byte) Manifest {
	var manifest Manifest
	err := yaml.Unmarshal(data, &manifest)
	if err != nil {
		err = displaySyntaxError(data, err)
		panic(err)
	}
	return manifest
}
<commit_msg>IO_ERR for issues when opening config file<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/michaelsauter/crane/print"
	"gopkg.in/v1/yaml"
	"io/ioutil"
	"os"
	"path/filepath"
)

type Manifest struct {
	Containers Containers          `json:"containers" yaml:"containers"`
	Groups     map[string][]string `json:"groups" yaml:"groups"`
}

func determineTargetedContainers(manifest Manifest, specifiedGroup string) []string {
	// If group is not given, all containers
	if len(specifiedGroup) == 0 {
		var containers []string
		for i := 0; i < len(manifest.Containers); i++ {
			containers = append(containers, manifest.Containers[i].Name)
		}
		return containers
	}
	// Select specified group from listed groups
	for name, containers := range manifest.Groups {
		if name == specifiedGroup {
			return containers
		}
	}
	// Otherwise, the group is just the specified container
	return append([]string{}, specifiedGroup)
}

func getManifest(options Options) Manifest {
	if len(options.config) > 0 {
		return unmarshalJSON([]byte(options.config))
	} else {
		for _, f := range manifestFiles() {
			if _, err := os.Stat(f); err == nil {
				return readCraneData(f)
			}
		}
	}
	panic(StatusError{fmt.Errorf("No configuration found %v", manifestFiles()), 78})
}

func getContainers(options Options) Containers {
	manifest := getManifest(options)
	targetedContainers := determineTargetedContainers(manifest, options.group)
	return manifest.Containers.filter(targetedContainers)
}

func readCraneData(filename string) Manifest {
	data, err := ioutil.ReadFile(filename)
	if err != nil {
		panic(StatusError{err, 74})
	}

	if filename == "Cranefile" {
		print.Notice("Using a Cranefile is deprecated. Please use crane.json/crane.yaml instead.\n")
	}

	ext := filepath.Ext(filename)
	if ext == ".json" {
		return unmarshalJSON(data)
	} else if ext == ".yml" || ext == ".yaml" {
		return unmarshalYAML(data)
	} else if ext == "" {
		return unmarshalJSON(data)
	} else {
		panic("Unrecognized file extension")
	}
}

// Thanks to https://github.com/markpeek/packer/commit/5bf33a0e91b2318a40c42e9bf855dcc8dd4cdec5
func displaySyntaxError(data []byte, syntaxError error) (err error) {
	syntax, ok := syntaxError.(*json.SyntaxError)
	if !ok {
		err = syntaxError
		return
	}
	newline := []byte{'\x0a'}
	space := []byte{' '}

	start, end := bytes.LastIndex(data[:syntax.Offset], newline)+1, len(data)
	if idx := bytes.Index(data[start:], newline); idx >= 0 {
		end = start + idx
	}

	line, pos := bytes.Count(data[:start], newline)+1, int(syntax.Offset)-start-1

	err = fmt.Errorf("\nError in line %d: %s \n%s\n%s^", line, syntaxError, data[start:end], bytes.Repeat(space, pos))
	return
}

func unmarshalJSON(data []byte) Manifest {
	var manifest Manifest
	err := json.Unmarshal(data, &manifest)
	if err != nil {
		err = displaySyntaxError(data, err)
		panic(err)
	}
	return manifest
}

func unmarshalYAML(data []byte) Manifest {
	var manifest Manifest
	err := yaml.Unmarshal(data, &manifest)
	if err != nil {
		err = displaySyntaxError(data, err)
		panic(err)
	}
	return manifest
}
<|endoftext|>


<commit_before>// Copyright (c) 2013 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package main

import (
	"fmt"
	"github.com/conformal/btcdb"
	_ "github.com/conformal/btcdb/ldb"
	"github.com/conformal/btcutil"
	"github.com/conformal/btcwire"
	"github.com/conformal/go-flags"
	"net"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

const (
	defaultConfigFilename = "btcd.conf"
	defaultDataDirname    = "data"
	defaultLogLevel       = "info"
	defaultBtcnet         = btcwire.MainNet
	defaultMaxPeers       = 125
	defaultBanDuration    = time.Hour * 24
	defaultVerifyEnabled  = false
	defaultDbType         = "leveldb"
)

var (
	btcdHomeDir        = btcutil.AppDataDir("btcd", false)
	defaultConfigFile  = filepath.Join(btcdHomeDir, defaultConfigFilename)
	defaultDataDir     = filepath.Join(btcdHomeDir, defaultDataDirname)
	defaultListener    = net.JoinHostPort("", netParams(defaultBtcnet).listenPort)
	knownDbTypes       = btcdb.SupportedDBs()
	defaultRPCKeyFile  = filepath.Join(btcdHomeDir, "rpc.key")
	defaultRPCCertFile = filepath.Join(btcdHomeDir, "rpc.cert")
)

// config defines the configuration options for btcd.
//
// See loadConfig for details on the configuration load process.
type config struct {
	ShowVersion        bool          `short:"V" long:"version" description:"Display version information and exit"`
	ConfigFile         string        `short:"C" long:"configfile" description:"Path to configuration file"`
	DataDir            string        `short:"b" long:"datadir" description:"Directory to store data"`
	AddPeers           []string      `short:"a" long:"addpeer" description:"Add a peer to connect with at startup"`
	ConnectPeers       []string      `long:"connect" description:"Connect only to the specified peers at startup"`
	DisableListen      bool          `long:"nolisten" description:"Disable listening for incoming connections -- NOTE: Listening is automatically disabled if the --connect or --proxy options are used without also specifying listen interfaces via --listen"`
	Listeners          []string      `long:"listen" description:"Add an interface/port to listen for connections (default all interfaces port: 8333, testnet: 18333)"`
	MaxPeers           int           `long:"maxpeers" description:"Max number of inbound and outbound peers"`
	BanDuration        time.Duration `long:"banduration" description:"How long to ban misbehaving peers.  Valid time units are {s, m, h}.  Minimum 1 second"`
	RPCUser            string        `short:"u" long:"rpcuser" description:"Username for RPC connections"`
	RPCPass            string        `short:"P" long:"rpcpass" default-mask:"-" description:"Password for RPC connections"`
	RPCListeners       []string      `long:"rpclisten" description:"Add an interface/port to listen for RPC connections (default port: 8334, testnet: 18334)"`
	RPCCert            string        `long:"rpccert" description:"File containing the certificate file"`
	RPCKey             string        `long:"rpckey" description:"File containing the certificate key"`
	DisableRPC         bool          `long:"norpc" description:"Disable built-in RPC server -- NOTE: The RPC server is disabled by default if no rpcuser/rpcpass is specified"`
	DisableDNSSeed     bool          `long:"nodnsseed" description:"Disable DNS seeding for peers"`
	Proxy              string        `long:"proxy" description:"Connect via SOCKS5 proxy (eg. 127.0.0.1:9050)"`
	ProxyUser          string        `long:"proxyuser" description:"Username for proxy server"`
	ProxyPass          string        `long:"proxypass" default-mask:"-" description:"Password for proxy server"`
	UseTor             bool          `long:"tor" description:"Specifies the proxy server used is a Tor node"`
	TestNet3           bool          `long:"testnet" description:"Use the test network"`
	RegressionTest     bool          `long:"regtest" description:"Use the regression test network"`
	DisableCheckpoints bool          `long:"nocheckpoints" description:"Disable built-in checkpoints.  Don't do this unless you know what you're doing."`
	DbType             string        `long:"dbtype" description:"Database backend to use for the Block Chain"`
	Profile            string        `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"`
	CpuProfile         string        `long:"cpuprofile" description:"Write CPU profile to the specified file"`
	DebugLevel         string        `short:"d" long:"debuglevel" description:"Logging level {trace, debug, info, warn, error, critical}"`
}

// cleanAndExpandPath expands environement variables and leading ~ in the
// passed path, cleans the result, and returns it.
func cleanAndExpandPath(path string) string {
	// Expand initial ~ to OS specific home directory.
	if strings.HasPrefix(path, "~") {
		homeDir := filepath.Dir(btcdHomeDir)
		path = strings.Replace(path, "~", homeDir, 1)
	}

	// NOTE: The os.ExpandEnv doesn't work with Windows-style %VARIABLE%,
	// but they variables can still be expanded via POSIX-style $VARIABLE.
	return filepath.Clean(os.ExpandEnv(path))
}

// validLogLevel returns whether or not logLevel is a valid debug log level.
func validLogLevel(logLevel string) bool {
	switch logLevel {
	case "trace":
		fallthrough
	case "debug":
		fallthrough
	case "info":
		fallthrough
	case "warn":
		fallthrough
	case "error":
		fallthrough
	case "critical":
		return true
	}
	return false
}

// validDbType returns whether or not dbType is a supported database type.
func validDbType(dbType string) bool {
	for _, knownType := range knownDbTypes {
		if dbType == knownType {
			return true
		}
	}

	return false
}

// removeDuplicateAddresses returns a new slice with all duplicate entries in
// addrs removed.
func removeDuplicateAddresses(addrs []string) []string {
	result := make([]string, 0)
	seen := map[string]bool{}
	for _, val := range addrs {
		if _, ok := seen[val]; !ok {
			result = append(result, val)
			seen[val] = true
		}
	}
	return result
}

// normalizeAddress returns addr with the passed default port appended if
// there is not already a port specified.
func normalizeAddress(addr, defaultPort string) string {
	_, _, err := net.SplitHostPort(addr)
	if err != nil {
		return net.JoinHostPort(addr, defaultPort)
	}
	return addr
}

// normalizeAddresses returns a new slice with all the passed peer addresses
// normalized with the given default port, and all duplicates removed.
func normalizeAddresses(addrs []string, defaultPort string) []string {
	for i, addr := range addrs {
		addrs[i] = normalizeAddress(addr, defaultPort)
	}

	return removeDuplicateAddresses(addrs)
}

// filesExists reports whether the named file or directory exists.
func fileExists(name string) bool {
	if _, err := os.Stat(name); err != nil {
		if os.IsNotExist(err) {
			return false
		}
	}
	return true
}

// loadConfig initializes and parses the config using a config file and command
// line options.
//
// The configuration proceeds as follows:
// 	1) Start with a default config with sane settings
// 	2) Pre-parse the command line to check for an alternative config file
// 	3) Load configuration file overwriting defaults with any specified options
// 	4) Parse CLI options and overwrite/add any specified options
//
// The above results in btcd functioning properly without any config settings
// while still allowing the user to override settings with config files and
// command line options.  Command line options always take precedence.
func loadConfig() (*config, []string, error) {
	// Default config.
	cfg := config{
		DebugLevel:  defaultLogLevel,
		MaxPeers:    defaultMaxPeers,
		BanDuration: defaultBanDuration,
		ConfigFile:  defaultConfigFile,
		DataDir:     defaultDataDir,
		DbType:      defaultDbType,
		RPCKey:      defaultRPCKeyFile,
		RPCCert:     defaultRPCCertFile,
	}

	// Create the home directory if it doesn't already exist.
	err := os.MkdirAll(btcdHomeDir, 0700)
	if err != nil {
		log.Errorf("%v", err)
		os.Exit(-1)
	}

	// Pre-parse the command line options to see if an alternative config
	// file or the version flag was specified.  Any errors can be ignored
	// here since they will be caught be the final parse below.
	preCfg := cfg
	preParser := flags.NewParser(&preCfg, flags.None)
	preParser.Parse()

	// Show the version and exit if the version flag was specified.
	if preCfg.ShowVersion {
		appName := filepath.Base(os.Args[0])
		appName = strings.TrimSuffix(appName, filepath.Ext(appName))
		fmt.Println(appName, "version", version())
		os.Exit(0)
	}

	// Load additional config from file.
	parser := flags.NewParser(&cfg, flags.Default)
	if !preCfg.RegressionTest || preCfg.ConfigFile != defaultConfigFile {
		err := parser.ParseIniFile(preCfg.ConfigFile)
		if err != nil {
			if _, ok := err.(*os.PathError); !ok {
				fmt.Fprintln(os.Stderr, err)
				parser.WriteHelp(os.Stderr)
				return nil, nil, err
			}
			log.Warnf("%v", err)
		}
	}

	// Don't add peers from the config file when in regression test mode.
	if preCfg.RegressionTest && len(cfg.AddPeers) > 0 {
		cfg.AddPeers = nil
	}

	// Parse command line options again to ensure they take precedence.
	remainingArgs, err := parser.Parse()
	if err != nil {
		if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
			parser.WriteHelp(os.Stderr)
		}
		return nil, nil, err
	}

	// The two test networks can't be selected simultaneously.
	if cfg.TestNet3 && cfg.RegressionTest {
		str := "%s: The testnet and regtest params can't be used " +
			"together -- choose one of the two"
		err := fmt.Errorf(str, "loadConfig")
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// Choose the active network params based on the testnet and regression
	// test net flags.
	if cfg.TestNet3 {
		activeNetParams = netParams(btcwire.TestNet3)
	} else if cfg.RegressionTest {
		activeNetParams = netParams(btcwire.TestNet)
	}

	// Validate debug log level.
	if !validLogLevel(cfg.DebugLevel) {
		str := "%s: The specified debug level [%v] is invalid"
		err := fmt.Errorf(str, "loadConfig", cfg.DebugLevel)
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// Validate database type.
	if !validDbType(cfg.DbType) {
		str := "%s: The specified database type [%v] is invalid -- " +
			"supported types %v"
		err := fmt.Errorf(str, "loadConfig", cfg.DbType, knownDbTypes)
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// Validate profile port number
	if cfg.Profile != "" {
		profilePort, err := strconv.Atoi(cfg.Profile)
		if err != nil || profilePort < 1024 || profilePort > 65535 {
			str := "%s: The profile port must be between 1024 and 65535"
			err := fmt.Errorf(str, "loadConfig")
			fmt.Fprintln(os.Stderr, err)
			parser.WriteHelp(os.Stderr)
			return nil, nil, err
		}
	}

	// Append the network type to the data directory so it is "namespaced"
	// per network.  In addition to the block database, there are other
	// pieces of data that are saved to disk such as address manager state.
	// All data is specific to a network, so namespacing the data directory
	// means each individual piece of serialized data does not have to
	// worry about changing names per network and such.
	cfg.DataDir = cleanAndExpandPath(cfg.DataDir)
	cfg.DataDir = filepath.Join(cfg.DataDir, activeNetParams.netName)

	// Don't allow ban durations that are too short.
	if cfg.BanDuration < time.Duration(time.Second) {
		str := "%s: The banduration option may not be less than 1s -- parsed [%v]"
		err := fmt.Errorf(str, "loadConfig", cfg.BanDuration)
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// --addPeer and --connect do not mix.
	if len(cfg.AddPeers) > 0 && len(cfg.ConnectPeers) > 0 {
		str := "%s: the --addpeer and --connect options can not be " +
			"mixed"
		err := fmt.Errorf(str, "loadConfig")
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// --tor requires --proxy to be set.
	if cfg.UseTor && cfg.Proxy == "" {
		str := "%s: the --tor option requires --proxy to be set"
		err := fmt.Errorf(str, "loadConfig")
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// --proxy or --connect without --listen disables listening.
	if (cfg.Proxy != "" || len(cfg.ConnectPeers) > 0) &&
		len(cfg.Listeners) == 0 {
		cfg.DisableListen = true
	}

	// Connect means no DNS seeding.
	if len(cfg.ConnectPeers) > 0 {
		cfg.DisableDNSSeed = true
	}

	// Add the default listener if none were specified. The default
	// listener is all addresses on the listen port for the network
	// we are to connect to.
	if len(cfg.Listeners) == 0 {
		cfg.Listeners = []string{
			net.JoinHostPort("", activeNetParams.listenPort),
		}
	}

	// The RPC server is disabled if no username or password is provided.
	if cfg.RPCUser == "" || cfg.RPCPass == "" {
		cfg.DisableRPC = true
	}

	if len(cfg.RPCListeners) == 0 {
		cfg.RPCListeners = []string{
			net.JoinHostPort("", activeNetParams.rpcPort),
		}
	}

	// Add default port to all listener addresses if needed and remove
	// duplicate addresses.
	cfg.Listeners = normalizeAddresses(cfg.Listeners,
		activeNetParams.listenPort)

	// Add default port to all rpc listener addresses if needed and remove
	// duplicate addresses.
	cfg.RPCListeners = normalizeAddresses(cfg.RPCListeners,
		activeNetParams.rpcPort)

	// Add default port to all added peer addresses if needed and remove
	// duplicate addresses.
	cfg.AddPeers = normalizeAddresses(cfg.AddPeers,
		activeNetParams.peerPort)
	cfg.ConnectPeers = normalizeAddresses(cfg.ConnectPeers,
		activeNetParams.peerPort)

	return &cfg, remainingArgs, nil
}
<commit_msg>Return error on dir create failure from loadConfig.<commit_after>// Copyright (c) 2013 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package main

import (
	"fmt"
	"github.com/conformal/btcdb"
	_ "github.com/conformal/btcdb/ldb"
	"github.com/conformal/btcutil"
	"github.com/conformal/btcwire"
	"github.com/conformal/go-flags"
	"net"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

const (
	defaultConfigFilename = "btcd.conf"
	defaultDataDirname    = "data"
	defaultLogLevel       = "info"
	defaultBtcnet         = btcwire.MainNet
	defaultMaxPeers       = 125
	defaultBanDuration    = time.Hour * 24
	defaultVerifyEnabled  = false
	defaultDbType         = "leveldb"
)

var (
	btcdHomeDir        = btcutil.AppDataDir("btcd", false)
	defaultConfigFile  = filepath.Join(btcdHomeDir, defaultConfigFilename)
	defaultDataDir     = filepath.Join(btcdHomeDir, defaultDataDirname)
	defaultListener    = net.JoinHostPort("", netParams(defaultBtcnet).listenPort)
	knownDbTypes       = btcdb.SupportedDBs()
	defaultRPCKeyFile  = filepath.Join(btcdHomeDir, "rpc.key")
	defaultRPCCertFile = filepath.Join(btcdHomeDir, "rpc.cert")
)

// config defines the configuration options for btcd.
//
// See loadConfig for details on the configuration load process.
type config struct {
	ShowVersion        bool          `short:"V" long:"version" description:"Display version information and exit"`
	ConfigFile         string        `short:"C" long:"configfile" description:"Path to configuration file"`
	DataDir            string        `short:"b" long:"datadir" description:"Directory to store data"`
	AddPeers           []string      `short:"a" long:"addpeer" description:"Add a peer to connect with at startup"`
	ConnectPeers       []string      `long:"connect" description:"Connect only to the specified peers at startup"`
	DisableListen      bool          `long:"nolisten" description:"Disable listening for incoming connections -- NOTE: Listening is automatically disabled if the --connect or --proxy options are used without also specifying listen interfaces via --listen"`
	Listeners          []string      `long:"listen" description:"Add an interface/port to listen for connections (default all interfaces port: 8333, testnet: 18333)"`
	MaxPeers           int           `long:"maxpeers" description:"Max number of inbound and outbound peers"`
	BanDuration        time.Duration `long:"banduration" description:"How long to ban misbehaving peers.  Valid time units are {s, m, h}.  Minimum 1 second"`
	RPCUser            string        `short:"u" long:"rpcuser" description:"Username for RPC connections"`
	RPCPass            string        `short:"P" long:"rpcpass" default-mask:"-" description:"Password for RPC connections"`
	RPCListeners       []string      `long:"rpclisten" description:"Add an interface/port to listen for RPC connections (default port: 8334, testnet: 18334)"`
	RPCCert            string        `long:"rpccert" description:"File containing the certificate file"`
	RPCKey             string        `long:"rpckey" description:"File containing the certificate key"`
	DisableRPC         bool          `long:"norpc" description:"Disable built-in RPC server -- NOTE: The RPC server is disabled by default if no rpcuser/rpcpass is specified"`
	DisableDNSSeed     bool          `long:"nodnsseed" description:"Disable DNS seeding for peers"`
	Proxy              string        `long:"proxy" description:"Connect via SOCKS5 proxy (eg. 127.0.0.1:9050)"`
	ProxyUser          string        `long:"proxyuser" description:"Username for proxy server"`
	ProxyPass          string        `long:"proxypass" default-mask:"-" description:"Password for proxy server"`
	UseTor             bool          `long:"tor" description:"Specifies the proxy server used is a Tor node"`
	TestNet3           bool          `long:"testnet" description:"Use the test network"`
	RegressionTest     bool          `long:"regtest" description:"Use the regression test network"`
	DisableCheckpoints bool          `long:"nocheckpoints" description:"Disable built-in checkpoints.  Don't do this unless you know what you're doing."`
	DbType             string        `long:"dbtype" description:"Database backend to use for the Block Chain"`
	Profile            string        `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"`
	CpuProfile         string        `long:"cpuprofile" description:"Write CPU profile to the specified file"`
	DebugLevel         string        `short:"d" long:"debuglevel" description:"Logging level {trace, debug, info, warn, error, critical}"`
}

// cleanAndExpandPath expands environement variables and leading ~ in the
// passed path, cleans the result, and returns it.
func cleanAndExpandPath(path string) string {
	// Expand initial ~ to OS specific home directory.
	if strings.HasPrefix(path, "~") {
		homeDir := filepath.Dir(btcdHomeDir)
		path = strings.Replace(path, "~", homeDir, 1)
	}

	// NOTE: The os.ExpandEnv doesn't work with Windows-style %VARIABLE%,
	// but they variables can still be expanded via POSIX-style $VARIABLE.
	return filepath.Clean(os.ExpandEnv(path))
}

// validLogLevel returns whether or not logLevel is a valid debug log level.
func validLogLevel(logLevel string) bool {
	switch logLevel {
	case "trace":
		fallthrough
	case "debug":
		fallthrough
	case "info":
		fallthrough
	case "warn":
		fallthrough
	case "error":
		fallthrough
	case "critical":
		return true
	}
	return false
}

// validDbType returns whether or not dbType is a supported database type.
func validDbType(dbType string) bool {
	for _, knownType := range knownDbTypes {
		if dbType == knownType {
			return true
		}
	}

	return false
}

// removeDuplicateAddresses returns a new slice with all duplicate entries in
// addrs removed.
func removeDuplicateAddresses(addrs []string) []string {
	result := make([]string, 0)
	seen := map[string]bool{}
	for _, val := range addrs {
		if _, ok := seen[val]; !ok {
			result = append(result, val)
			seen[val] = true
		}
	}
	return result
}

// normalizeAddress returns addr with the passed default port appended if
// there is not already a port specified.
func normalizeAddress(addr, defaultPort string) string {
	_, _, err := net.SplitHostPort(addr)
	if err != nil {
		return net.JoinHostPort(addr, defaultPort)
	}
	return addr
}

// normalizeAddresses returns a new slice with all the passed peer addresses
// normalized with the given default port, and all duplicates removed.
func normalizeAddresses(addrs []string, defaultPort string) []string {
	for i, addr := range addrs {
		addrs[i] = normalizeAddress(addr, defaultPort)
	}

	return removeDuplicateAddresses(addrs)
}

// filesExists reports whether the named file or directory exists.
func fileExists(name string) bool {
	if _, err := os.Stat(name); err != nil {
		if os.IsNotExist(err) {
			return false
		}
	}
	return true
}

// loadConfig initializes and parses the config using a config file and command
// line options.
//
// The configuration proceeds as follows:
// 	1) Start with a default config with sane settings
// 	2) Pre-parse the command line to check for an alternative config file
// 	3) Load configuration file overwriting defaults with any specified options
// 	4) Parse CLI options and overwrite/add any specified options
//
// The above results in btcd functioning properly without any config settings
// while still allowing the user to override settings with config files and
// command line options.  Command line options always take precedence.
func loadConfig() (*config, []string, error) {
	// Default config.
	cfg := config{
		DebugLevel:  defaultLogLevel,
		MaxPeers:    defaultMaxPeers,
		BanDuration: defaultBanDuration,
		ConfigFile:  defaultConfigFile,
		DataDir:     defaultDataDir,
		DbType:      defaultDbType,
		RPCKey:      defaultRPCKeyFile,
		RPCCert:     defaultRPCCertFile,
	}

	// Create the home directory if it doesn't already exist.
	err := os.MkdirAll(btcdHomeDir, 0700)
	if err != nil {
		log.Errorf("%v", err)
		return nil, nil, err
	}

	// Pre-parse the command line options to see if an alternative config
	// file or the version flag was specified.  Any errors can be ignored
	// here since they will be caught be the final parse below.
	preCfg := cfg
	preParser := flags.NewParser(&preCfg, flags.None)
	preParser.Parse()

	// Show the version and exit if the version flag was specified.
	if preCfg.ShowVersion {
		appName := filepath.Base(os.Args[0])
		appName = strings.TrimSuffix(appName, filepath.Ext(appName))
		fmt.Println(appName, "version", version())
		os.Exit(0)
	}

	// Load additional config from file.
	parser := flags.NewParser(&cfg, flags.Default)
	if !preCfg.RegressionTest || preCfg.ConfigFile != defaultConfigFile {
		err := parser.ParseIniFile(preCfg.ConfigFile)
		if err != nil {
			if _, ok := err.(*os.PathError); !ok {
				fmt.Fprintln(os.Stderr, err)
				parser.WriteHelp(os.Stderr)
				return nil, nil, err
			}
			log.Warnf("%v", err)
		}
	}

	// Don't add peers from the config file when in regression test mode.
	if preCfg.RegressionTest && len(cfg.AddPeers) > 0 {
		cfg.AddPeers = nil
	}

	// Parse command line options again to ensure they take precedence.
	remainingArgs, err := parser.Parse()
	if err != nil {
		if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
			parser.WriteHelp(os.Stderr)
		}
		return nil, nil, err
	}

	// The two test networks can't be selected simultaneously.
	if cfg.TestNet3 && cfg.RegressionTest {
		str := "%s: The testnet and regtest params can't be used " +
			"together -- choose one of the two"
		err := fmt.Errorf(str, "loadConfig")
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// Choose the active network params based on the testnet and regression
	// test net flags.
	if cfg.TestNet3 {
		activeNetParams = netParams(btcwire.TestNet3)
	} else if cfg.RegressionTest {
		activeNetParams = netParams(btcwire.TestNet)
	}

	// Validate debug log level.
	if !validLogLevel(cfg.DebugLevel) {
		str := "%s: The specified debug level [%v] is invalid"
		err := fmt.Errorf(str, "loadConfig", cfg.DebugLevel)
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// Validate database type.
	if !validDbType(cfg.DbType) {
		str := "%s: The specified database type [%v] is invalid -- " +
			"supported types %v"
		err := fmt.Errorf(str, "loadConfig", cfg.DbType, knownDbTypes)
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// Validate profile port number
	if cfg.Profile != "" {
		profilePort, err := strconv.Atoi(cfg.Profile)
		if err != nil || profilePort < 1024 || profilePort > 65535 {
			str := "%s: The profile port must be between 1024 and 65535"
			err := fmt.Errorf(str, "loadConfig")
			fmt.Fprintln(os.Stderr, err)
			parser.WriteHelp(os.Stderr)
			return nil, nil, err
		}
	}

	// Append the network type to the data directory so it is "namespaced"
	// per network.  In addition to the block database, there are other
	// pieces of data that are saved to disk such as address manager state.
	// All data is specific to a network, so namespacing the data directory
	// means each individual piece of serialized data does not have to
	// worry about changing names per network and such.
	cfg.DataDir = cleanAndExpandPath(cfg.DataDir)
	cfg.DataDir = filepath.Join(cfg.DataDir, activeNetParams.netName)

	// Don't allow ban durations that are too short.
	if cfg.BanDuration < time.Duration(time.Second) {
		str := "%s: The banduration option may not be less than 1s -- parsed [%v]"
		err := fmt.Errorf(str, "loadConfig", cfg.BanDuration)
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// --addPeer and --connect do not mix.
	if len(cfg.AddPeers) > 0 && len(cfg.ConnectPeers) > 0 {
		str := "%s: the --addpeer and --connect options can not be " +
			"mixed"
		err := fmt.Errorf(str, "loadConfig")
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// --tor requires --proxy to be set.
	if cfg.UseTor && cfg.Proxy == "" {
		str := "%s: the --tor option requires --proxy to be set"
		err := fmt.Errorf(str, "loadConfig")
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// --proxy or --connect without --listen disables listening.
	if (cfg.Proxy != "" || len(cfg.ConnectPeers) > 0) &&
		len(cfg.Listeners) == 0 {
		cfg.DisableListen = true
	}

	// Connect means no DNS seeding.
	if len(cfg.ConnectPeers) > 0 {
		cfg.DisableDNSSeed = true
	}

	// Add the default listener if none were specified. The default
	// listener is all addresses on the listen port for the network
	// we are to connect to.
	if len(cfg.Listeners) == 0 {
		cfg.Listeners = []string{
			net.JoinHostPort("", activeNetParams.listenPort),
		}
	}

	// The RPC server is disabled if no username or password is provided.
	if cfg.RPCUser == "" || cfg.RPCPass == "" {
		cfg.DisableRPC = true
	}

	if len(cfg.RPCListeners) == 0 {
		cfg.RPCListeners = []string{
			net.JoinHostPort("", activeNetParams.rpcPort),
		}
	}

	// Add default port to all listener addresses if needed and remove
	// duplicate addresses.
	cfg.Listeners = normalizeAddresses(cfg.Listeners,
		activeNetParams.listenPort)

	// Add default port to all rpc listener addresses if needed and remove
	// duplicate addresses.
	cfg.RPCListeners = normalizeAddresses(cfg.RPCListeners,
		activeNetParams.rpcPort)

	// Add default port to all added peer addresses if needed and remove
	// duplicate addresses.
	cfg.AddPeers = normalizeAddresses(cfg.AddPeers,
		activeNetParams.peerPort)
	cfg.ConnectPeers = normalizeAddresses(cfg.ConnectPeers,
		activeNetParams.peerPort)

	return &cfg, remainingArgs, nil
}
<|endoftext|>


<commit_before>// Copyright 2015 The Chromium 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 target

import (
	"flag"
	"net"
	"os"
	"regexp"
	"strings"
)

// Flags defines command line flags related to tsmon targets.  Use NewFlags()
// to get a Flags struct with sensible default values.
type Flags struct {
	TargetType      string
	DeviceHostname  string
	DeviceRegion    string
	DeviceRole      string
	DeviceNetwork   string
	TaskServiceName string
	TaskJobName     string
	TaskRegion      string
	TaskHostname    string
	TaskNumber      int
}

// NewFlags returns a Flags struct with sensible default values.  Hostname,
// region and network flags are expensive to compute, so get assigned default
// values later in SetDefaultsFromHostname.
func NewFlags() Flags {
	return Flags{
		TargetType:      "device",
		DeviceHostname:  "",
		DeviceRegion:    "",
		DeviceRole:      "default",
		DeviceNetwork:   "",
		TaskServiceName: "",
		TaskJobName:     "",
		TaskRegion:      "",
		TaskHostname:    "",
		TaskNumber:      0,
	}
}

// SetDefaultsFromHostname computes the expensive default values for hostname,
// region and network fields.
func (fl *Flags) SetDefaultsFromHostname() {
	hostname, region := getFQDN()
	network := getNetwork(hostname)

	if fl.DeviceHostname == "" {
		fl.DeviceHostname = hostname
	}
	if fl.DeviceRegion == "" {
		fl.DeviceRegion = region
	}
	if fl.DeviceNetwork == "" {
		fl.DeviceNetwork = network
	}
	if fl.TaskRegion == "" {
		fl.TaskRegion = region
	}
	if fl.TaskHostname == "" {
		fl.TaskHostname = hostname
	}
}

// Register adds tsmon target related flags to a FlagSet.
func (fl *Flags) Register(f *flag.FlagSet) {
	f.StringVar(&fl.TargetType, "ts-mon-target-type", fl.TargetType,
		"the type of target that is being monitored ('device' or 'task')")
	f.StringVar(&fl.DeviceHostname, "ts-mon-device-hostname", fl.DeviceHostname,
		"name of this device")
	f.StringVar(&fl.DeviceRegion, "ts-mon-device-region", fl.DeviceRegion,
		"name of the region this devices lives in")
	f.StringVar(&fl.DeviceRole, "ts-mon-device-role", fl.DeviceRole,
		"role of the device")
	f.StringVar(&fl.DeviceNetwork, "ts-mon-device-network", fl.DeviceNetwork,
		"name of the network this device is connected to")
	f.StringVar(&fl.TaskServiceName, "ts-mon-task-service-name", fl.TaskServiceName,
		"name of the service being monitored")
	f.StringVar(&fl.TaskJobName, "ts-mon-task-job-name", fl.TaskJobName,
		"name of this job instance of the task")
	f.StringVar(&fl.TaskRegion, "ts-mon-task-region", fl.TaskRegion,
		"name of the region in which this task is running")
	f.StringVar(&fl.TaskHostname, "ts-mon-task-hostname", fl.TaskHostname,
		"name of the host on which this task is running")
	f.IntVar(&fl.TaskNumber, "ts-mon-task-number", fl.TaskNumber,
		"number (e.g. for replication) of this instance of this task")
}

func getFQDN() (string, string) {
	if addrs, err := net.InterfaceAddrs(); err == nil {
		for _, addr := range addrs {
			if ipNet, ok := addr.(*net.IPNet); ok {
				if names, err := net.LookupAddr(ipNet.IP.String()); err == nil {
					for _, name := range names {
						parts := strings.Split(name, ".")
						if len(parts) > 1 {
							return strings.ToLower(parts[0]), strings.ToLower(parts[1])
						}
					}
				}
			}
		}
	}
	if hostname, err := os.Hostname(); err != nil {
		return strings.ToLower(hostname), "unknown"
	}
	return "unknown", "unknown"
}

func getNetwork(hostname string) string {
	if m := regexp.MustCompile(`^([\w-]*?-[acm]|master)(\d+)a?$`).FindStringSubmatch(hostname); m != nil {
		return m[2]
	}
	return ""
}
<commit_msg>Oops - correctly get the hostname from os.Hostname when the network is down<commit_after>// Copyright 2015 The Chromium 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 target

import (
	"flag"
	"net"
	"os"
	"regexp"
	"strings"
)

// Flags defines command line flags related to tsmon targets.  Use NewFlags()
// to get a Flags struct with sensible default values.
type Flags struct {
	TargetType      string
	DeviceHostname  string
	DeviceRegion    string
	DeviceRole      string
	DeviceNetwork   string
	TaskServiceName string
	TaskJobName     string
	TaskRegion      string
	TaskHostname    string
	TaskNumber      int
}

// NewFlags returns a Flags struct with sensible default values.  Hostname,
// region and network flags are expensive to compute, so get assigned default
// values later in SetDefaultsFromHostname.
func NewFlags() Flags {
	return Flags{
		TargetType:      "device",
		DeviceHostname:  "",
		DeviceRegion:    "",
		DeviceRole:      "default",
		DeviceNetwork:   "",
		TaskServiceName: "",
		TaskJobName:     "",
		TaskRegion:      "",
		TaskHostname:    "",
		TaskNumber:      0,
	}
}

// SetDefaultsFromHostname computes the expensive default values for hostname,
// region and network fields.
func (fl *Flags) SetDefaultsFromHostname() {
	hostname, region := getFQDN()
	network := getNetwork(hostname)

	if fl.DeviceHostname == "" {
		fl.DeviceHostname = hostname
	}
	if fl.DeviceRegion == "" {
		fl.DeviceRegion = region
	}
	if fl.DeviceNetwork == "" {
		fl.DeviceNetwork = network
	}
	if fl.TaskRegion == "" {
		fl.TaskRegion = region
	}
	if fl.TaskHostname == "" {
		fl.TaskHostname = hostname
	}
}

// Register adds tsmon target related flags to a FlagSet.
func (fl *Flags) Register(f *flag.FlagSet) {
	f.StringVar(&fl.TargetType, "ts-mon-target-type", fl.TargetType,
		"the type of target that is being monitored ('device' or 'task')")
	f.StringVar(&fl.DeviceHostname, "ts-mon-device-hostname", fl.DeviceHostname,
		"name of this device")
	f.StringVar(&fl.DeviceRegion, "ts-mon-device-region", fl.DeviceRegion,
		"name of the region this devices lives in")
	f.StringVar(&fl.DeviceRole, "ts-mon-device-role", fl.DeviceRole,
		"role of the device")
	f.StringVar(&fl.DeviceNetwork, "ts-mon-device-network", fl.DeviceNetwork,
		"name of the network this device is connected to")
	f.StringVar(&fl.TaskServiceName, "ts-mon-task-service-name", fl.TaskServiceName,
		"name of the service being monitored")
	f.StringVar(&fl.TaskJobName, "ts-mon-task-job-name", fl.TaskJobName,
		"name of this job instance of the task")
	f.StringVar(&fl.TaskRegion, "ts-mon-task-region", fl.TaskRegion,
		"name of the region in which this task is running")
	f.StringVar(&fl.TaskHostname, "ts-mon-task-hostname", fl.TaskHostname,
		"name of the host on which this task is running")
	f.IntVar(&fl.TaskNumber, "ts-mon-task-number", fl.TaskNumber,
		"number (e.g. for replication) of this instance of this task")
}

func getFQDN() (string, string) {
	if addrs, err := net.InterfaceAddrs(); err == nil {
		for _, addr := range addrs {
			if ipNet, ok := addr.(*net.IPNet); ok {
				if names, err := net.LookupAddr(ipNet.IP.String()); err == nil {
					for _, name := range names {
						parts := strings.Split(name, ".")
						if len(parts) > 1 {
							return strings.ToLower(parts[0]), strings.ToLower(parts[1])
						}
					}
				}
			}
		}
	}
	if hostname, err := os.Hostname(); err == nil {
		parts := strings.Split(hostname, ".")
		if len(parts) > 1 {
			return strings.ToLower(parts[0]), strings.ToLower(parts[1])
		}
		return strings.ToLower(hostname), "unknown"
	}
	return "unknown", "unknown"
}

func getNetwork(hostname string) string {
	if m := regexp.MustCompile(`^([\w-]*?-[acm]|master)(\d+)a?$`).FindStringSubmatch(hostname); m != nil {
		return m[2]
	}
	return ""
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"io"
	"log"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/mitchellh/osext"
	"github.com/mitchellh/packer/packer"
	"github.com/mitchellh/packer/packer/plugin"
)

type config struct {
	DisableCheckpoint          bool `json:"disable_checkpoint"`
	DisableCheckpointSignature bool `json:"disable_checkpoint_signature"`
	PluginMinPort              uint
	PluginMaxPort              uint

	Builders       map[string]string
	PostProcessors map[string]string `json:"post-processors"`
	Provisioners   map[string]string
}

// ConfigFile returns the default path to the configuration file. On
// Unix-like systems this is the ".packerconfig" file in the home directory.
// On Windows, this is the "packer.config" file in the application data
// directory.
func ConfigFile() (string, error) {
	return configFile()
}

// ConfigDir returns the configuration directory for Packer.
func ConfigDir() (string, error) {
	return configDir()
}

// Decodes configuration in JSON format from the given io.Reader into
// the config object pointed to.
func decodeConfig(r io.Reader, c *config) error {
	decoder := json.NewDecoder(r)
	return decoder.Decode(c)
}

// Discover discovers plugins.
//
// Search the directory of the executable, then the plugins directory, and
// finally the CWD, in that order. Any conflicts will overwrite previously
// found plugins, in that order.
// Hence, the priority order is the reverse of the search order - i.e., the
// CWD has the highest priority.
func (c *config) Discover() error {
	// First, look in the same directory as the executable.
	exePath, err := osext.Executable()
	if err != nil {
		log.Printf("[ERR] Error loading exe directory: %s", err)
	} else {
		if err := c.discover(filepath.Dir(exePath)); err != nil {
			return err
		}
	}

	// Next, look in the plugins directory.
	dir, err := ConfigDir()
	if err != nil {
		log.Printf("[ERR] Error loading config directory: %s", err)
	} else {
		if err := c.discover(filepath.Join(dir, "plugins")); err != nil {
			return err
		}
	}

	// Last, look in the CWD.
	if err := c.discover("."); err != nil {
		return err
	}

	return nil
}

// This is a proper packer.BuilderFunc that can be used to load packer.Builder
// implementations from the defined plugins.
func (c *config) LoadBuilder(name string) (packer.Builder, error) {
	log.Printf("Loading builder: %s\n", name)
	bin, ok := c.Builders[name]
	if !ok {
		log.Printf("Builder not found: %s\n", name)
		return nil, nil
	}

	return c.pluginClient(bin).Builder()
}

// This is a proper implementation of packer.HookFunc that can be used
// to load packer.Hook implementations from the defined plugins.
func (c *config) LoadHook(name string) (packer.Hook, error) {
	log.Printf("Loading hook: %s\n", name)
	return c.pluginClient(name).Hook()
}

// This is a proper packer.PostProcessorFunc that can be used to load
// packer.PostProcessor implementations from defined plugins.
func (c *config) LoadPostProcessor(name string) (packer.PostProcessor, error) {
	log.Printf("Loading post-processor: %s", name)
	bin, ok := c.PostProcessors[name]
	if !ok {
		log.Printf("Post-processor not found: %s", name)
		return nil, nil
	}

	return c.pluginClient(bin).PostProcessor()
}

// This is a proper packer.ProvisionerFunc that can be used to load
// packer.Provisioner implementations from defined plugins.
func (c *config) LoadProvisioner(name string) (packer.Provisioner, error) {
	log.Printf("Loading provisioner: %s\n", name)
	bin, ok := c.Provisioners[name]
	if !ok {
		log.Printf("Provisioner not found: %s\n", name)
		return nil, nil
	}

	return c.pluginClient(bin).Provisioner()
}

func (c *config) discover(path string) error {
	var err error

	if !filepath.IsAbs(path) {
		path, err = filepath.Abs(path)
		if err != nil {
			return err
		}
	}

	err = c.discoverSingle(
		filepath.Join(path, "packer-builder-*"), &c.Builders)
	if err != nil {
		return err
	}

	err = c.discoverSingle(
		filepath.Join(path, "packer-post-processor-*"), &c.PostProcessors)
	if err != nil {
		return err
	}

	err = c.discoverSingle(
		filepath.Join(path, "packer-provisioner-*"), &c.Provisioners)
	if err != nil {
		return err
	}

	return nil
}

func (c *config) discoverSingle(glob string, m *map[string]string) error {
	matches, err := filepath.Glob(glob)
	if err != nil {
		return err
	}

	if *m == nil {
		*m = make(map[string]string)
	}

	prefix := filepath.Base(glob)
	prefix = prefix[:strings.Index(prefix, "*")]
	for _, match := range matches {
		file := filepath.Base(match)

		// One Windows, ignore any plugins that don't end in .exe.
		// We could do a full PATHEXT parse, but this is probably good enough.
		if runtime.GOOS == "windows" && filepath.Ext(file) != "exe" {
			log.Printf(
				"[DEBUG] Ignoring plugin match %s, no exe extension",
				match)
			continue
		}

		// If the filename has a ".", trim up to there
		if idx := strings.Index(file, "."); idx >= 0 {
			file = file[:idx]
		}

		// Look for foo-bar-baz. The plugin name is "baz"
		plugin := file[len(prefix):]
		log.Printf("[DEBUG] Discovered plugin: %s = %s", plugin, match)
		(*m)[plugin] = match
	}

	return nil
}

func (c *config) pluginClient(path string) *plugin.Client {
	originalPath := path

	// First attempt to find the executable by consulting the PATH.
	path, err := exec.LookPath(path)
	if err != nil {
		// If that doesn't work, look for it in the same directory
		// as the `packer` executable (us).
		log.Printf("Plugin could not be found. Checking same directory as executable.")
		exePath, err := osext.Executable()
		if err != nil {
			log.Printf("Couldn't get current exe path: %s", err)
		} else {
			log.Printf("Current exe path: %s", exePath)
			path = filepath.Join(filepath.Dir(exePath), filepath.Base(originalPath))
		}
	}

	// If everything failed, just use the original path and let the error
	// bubble through.
	if path == "" {
		path = originalPath
	}

	log.Printf("Creating plugin client for path: %s", path)
	var config plugin.ClientConfig
	config.Cmd = exec.Command(path)
	config.Managed = true
	config.MinPort = c.PluginMinPort
	config.MaxPort = c.PluginMaxPort
	return plugin.NewClient(&config)
}
<commit_msg>lowercase the extension of plugins just in case<commit_after>package main

import (
	"encoding/json"
	"io"
	"log"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/mitchellh/osext"
	"github.com/mitchellh/packer/packer"
	"github.com/mitchellh/packer/packer/plugin"
)

type config struct {
	DisableCheckpoint          bool `json:"disable_checkpoint"`
	DisableCheckpointSignature bool `json:"disable_checkpoint_signature"`
	PluginMinPort              uint
	PluginMaxPort              uint

	Builders       map[string]string
	PostProcessors map[string]string `json:"post-processors"`
	Provisioners   map[string]string
}

// ConfigFile returns the default path to the configuration file. On
// Unix-like systems this is the ".packerconfig" file in the home directory.
// On Windows, this is the "packer.config" file in the application data
// directory.
func ConfigFile() (string, error) {
	return configFile()
}

// ConfigDir returns the configuration directory for Packer.
func ConfigDir() (string, error) {
	return configDir()
}

// Decodes configuration in JSON format from the given io.Reader into
// the config object pointed to.
func decodeConfig(r io.Reader, c *config) error {
	decoder := json.NewDecoder(r)
	return decoder.Decode(c)
}

// Discover discovers plugins.
//
// Search the directory of the executable, then the plugins directory, and
// finally the CWD, in that order. Any conflicts will overwrite previously
// found plugins, in that order.
// Hence, the priority order is the reverse of the search order - i.e., the
// CWD has the highest priority.
func (c *config) Discover() error {
	// First, look in the same directory as the executable.
	exePath, err := osext.Executable()
	if err != nil {
		log.Printf("[ERR] Error loading exe directory: %s", err)
	} else {
		if err := c.discover(filepath.Dir(exePath)); err != nil {
			return err
		}
	}

	// Next, look in the plugins directory.
	dir, err := ConfigDir()
	if err != nil {
		log.Printf("[ERR] Error loading config directory: %s", err)
	} else {
		if err := c.discover(filepath.Join(dir, "plugins")); err != nil {
			return err
		}
	}

	// Last, look in the CWD.
	if err := c.discover("."); err != nil {
		return err
	}

	return nil
}

// This is a proper packer.BuilderFunc that can be used to load packer.Builder
// implementations from the defined plugins.
func (c *config) LoadBuilder(name string) (packer.Builder, error) {
	log.Printf("Loading builder: %s\n", name)
	bin, ok := c.Builders[name]
	if !ok {
		log.Printf("Builder not found: %s\n", name)
		return nil, nil
	}

	return c.pluginClient(bin).Builder()
}

// This is a proper implementation of packer.HookFunc that can be used
// to load packer.Hook implementations from the defined plugins.
func (c *config) LoadHook(name string) (packer.Hook, error) {
	log.Printf("Loading hook: %s\n", name)
	return c.pluginClient(name).Hook()
}

// This is a proper packer.PostProcessorFunc that can be used to load
// packer.PostProcessor implementations from defined plugins.
func (c *config) LoadPostProcessor(name string) (packer.PostProcessor, error) {
	log.Printf("Loading post-processor: %s", name)
	bin, ok := c.PostProcessors[name]
	if !ok {
		log.Printf("Post-processor not found: %s", name)
		return nil, nil
	}

	return c.pluginClient(bin).PostProcessor()
}

// This is a proper packer.ProvisionerFunc that can be used to load
// packer.Provisioner implementations from defined plugins.
func (c *config) LoadProvisioner(name string) (packer.Provisioner, error) {
	log.Printf("Loading provisioner: %s\n", name)
	bin, ok := c.Provisioners[name]
	if !ok {
		log.Printf("Provisioner not found: %s\n", name)
		return nil, nil
	}

	return c.pluginClient(bin).Provisioner()
}

func (c *config) discover(path string) error {
	var err error

	if !filepath.IsAbs(path) {
		path, err = filepath.Abs(path)
		if err != nil {
			return err
		}
	}

	err = c.discoverSingle(
		filepath.Join(path, "packer-builder-*"), &c.Builders)
	if err != nil {
		return err
	}

	err = c.discoverSingle(
		filepath.Join(path, "packer-post-processor-*"), &c.PostProcessors)
	if err != nil {
		return err
	}

	err = c.discoverSingle(
		filepath.Join(path, "packer-provisioner-*"), &c.Provisioners)
	if err != nil {
		return err
	}

	return nil
}

func (c *config) discoverSingle(glob string, m *map[string]string) error {
	matches, err := filepath.Glob(glob)
	if err != nil {
		return err
	}

	if *m == nil {
		*m = make(map[string]string)
	}

	prefix := filepath.Base(glob)
	prefix = prefix[:strings.Index(prefix, "*")]
	for _, match := range matches {
		file := filepath.Base(match)

		// One Windows, ignore any plugins that don't end in .exe.
		// We could do a full PATHEXT parse, but this is probably good enough.
		if runtime.GOOS == "windows" && strings.ToLower(filepath.Ext(file)) != "exe" {
			log.Printf(
				"[DEBUG] Ignoring plugin match %s, no exe extension",
				match)
			continue
		}

		// If the filename has a ".", trim up to there
		if idx := strings.Index(file, "."); idx >= 0 {
			file = file[:idx]
		}

		// Look for foo-bar-baz. The plugin name is "baz"
		plugin := file[len(prefix):]
		log.Printf("[DEBUG] Discovered plugin: %s = %s", plugin, match)
		(*m)[plugin] = match
	}

	return nil
}

func (c *config) pluginClient(path string) *plugin.Client {
	originalPath := path

	// First attempt to find the executable by consulting the PATH.
	path, err := exec.LookPath(path)
	if err != nil {
		// If that doesn't work, look for it in the same directory
		// as the `packer` executable (us).
		log.Printf("Plugin could not be found. Checking same directory as executable.")
		exePath, err := osext.Executable()
		if err != nil {
			log.Printf("Couldn't get current exe path: %s", err)
		} else {
			log.Printf("Current exe path: %s", exePath)
			path = filepath.Join(filepath.Dir(exePath), filepath.Base(originalPath))
		}
	}

	// If everything failed, just use the original path and let the error
	// bubble through.
	if path == "" {
		path = originalPath
	}

	log.Printf("Creating plugin client for path: %s", path)
	var config plugin.ClientConfig
	config.Cmd = exec.Command(path)
	config.Managed = true
	config.MinPort = c.PluginMinPort
	config.MaxPort = c.PluginMaxPort
	return plugin.NewClient(&config)
}
<|endoftext|>


<commit_before>// See LICENSE.txt for licensing information.

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"time"
)

const (
	CFG_VER = 1
)

type Config struct {
	Root    string  // absolute path for game directories
	Games   []*Game // slice of games
	Version int     `json:Version,omitempty` // config format version
}

func (c *Config) PrintWhole() {
	if len(c.Games) < 1 {
		fmt.Println("No games defined")
		return
	}
	if flagVerbose {
		fmt.Printf("Config version: %d\n\n", c.Version)
	}
	fmt.Printf("%3s ", "#")
	c.Games[0].PrintHeader()
	for i, g := range c.Games {
		fmt.Printf("%3d ", i+1)
		g.Print()
	}
	fmt.Println()
}

func (c *Config) GetGame(name string) *Game {
	for _, g := range c.Games {
		if g.Name == name {
			return g
		}
	}
	return nil
}

func (c *Config) AddGame(name, path string) (gm *Game, err error) {
	r := filepath.Join(c.Root, name)
	if _, err = os.Stat(r); os.IsExist(err) {
		return
	}
	if _, err = os.Stat(path); os.IsNotExist(err) {
		return
	}
	if err = os.MkdirAll(r, 0777); err != nil {
		return
	}
	gm = &Game{
		Name:  name,
		Path:  path,
		Root:  r,
		Stamp: time.Now(),
		Saves: make([]*Save, 0),
	}
	c.Games = append(c.Games, gm)
	return gm, nil
}

func (c *Config) DelGame(name string) error {
	var i int
	var g *Game
	for i, g = range c.Games {
		if g.Name == name {
			break
		}
	}
	if g.Name != name {
		return fmt.Errorf(`Couldn't find game "%s"`, name)
	}
	if flagVerbose {
		fmt.Fprintln(os.Stderr, "removing all from:", g.Root)
	}
	if err := os.RemoveAll(g.Root); err != nil {
		return err
	}
	copy(c.Games[i:], c.Games[i+1:])
	c.Games[len(c.Games)-1] = nil
	c.Games = c.Games[:len(c.Games)-1]
	return nil
}

func (c *Config) CheckVer(min_ver int, print bool) bool {
	if c.Version >= min_ver {
		return true
	}

	if print {
		fmt.Fprintf(os.Stderr, "using old config format, please [migrate] (%d changes(s) behind)\n", CFG_VER-c.Version)
	}
	return false
}

func loadConfig(path string) (*Config, error) {
	if flagVerbose {
		fmt.Fprintln(os.Stderr, "loading config from", path)
	}
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	d, err := ioutil.ReadAll(f)
	if err != nil {
		return nil, err
	}
	var c *Config
	if err := json.Unmarshal(d, &c); err != nil {
		return nil, err
	}
	c.CheckVer(CFG_VER, flagVerbose)

	return c, nil
}

func (c *Config) Save(path string) error {
	if flagVerbose {
		fmt.Fprintln(os.Stderr, "saving config to", path)
	}
	f, err := os.Create(path)
	if err != nil {
		return err
	}
	defer f.Close()
	j := json.NewEncoder(f)
	return j.Encode(c)
}
<commit_msg>Use version const for main CFG_VER<commit_after>// See LICENSE.txt for licensing information.

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"time"
)

const (
	CFG_VER = CFG_VER_1
)

type Config struct {
	Root    string  // absolute path for game directories
	Games   []*Game // slice of games
	Version int     `json:Version,omitempty` // config format version
}

func (c *Config) PrintWhole() {
	if len(c.Games) < 1 {
		fmt.Println("No games defined")
		return
	}
	if flagVerbose {
		fmt.Printf("Config version: %d\n\n", c.Version)
	}
	fmt.Printf("%3s ", "#")
	c.Games[0].PrintHeader()
	for i, g := range c.Games {
		fmt.Printf("%3d ", i+1)
		g.Print()
	}
	fmt.Println()
}

func (c *Config) GetGame(name string) *Game {
	for _, g := range c.Games {
		if g.Name == name {
			return g
		}
	}
	return nil
}

func (c *Config) AddGame(name, path string) (gm *Game, err error) {
	r := filepath.Join(c.Root, name)
	if _, err = os.Stat(r); os.IsExist(err) {
		return
	}
	if _, err = os.Stat(path); os.IsNotExist(err) {
		return
	}
	if err = os.MkdirAll(r, 0777); err != nil {
		return
	}
	gm = &Game{
		Name:  name,
		Path:  path,
		Root:  r,
		Stamp: time.Now(),
		Saves: make([]*Save, 0),
	}
	c.Games = append(c.Games, gm)
	return gm, nil
}

func (c *Config) DelGame(name string) error {
	var i int
	var g *Game
	for i, g = range c.Games {
		if g.Name == name {
			break
		}
	}
	if g.Name != name {
		return fmt.Errorf(`Couldn't find game "%s"`, name)
	}
	if flagVerbose {
		fmt.Fprintln(os.Stderr, "removing all from:", g.Root)
	}
	if err := os.RemoveAll(g.Root); err != nil {
		return err
	}
	copy(c.Games[i:], c.Games[i+1:])
	c.Games[len(c.Games)-1] = nil
	c.Games = c.Games[:len(c.Games)-1]
	return nil
}

func (c *Config) CheckVer(min_ver int, print bool) bool {
	if c.Version >= min_ver {
		return true
	}

	if print {
		fmt.Fprintf(os.Stderr, "using old config format, please [migrate] (%d changes(s) behind)\n", CFG_VER-c.Version)
	}
	return false
}

func loadConfig(path string) (*Config, error) {
	if flagVerbose {
		fmt.Fprintln(os.Stderr, "loading config from", path)
	}
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	d, err := ioutil.ReadAll(f)
	if err != nil {
		return nil, err
	}
	var c *Config
	if err := json.Unmarshal(d, &c); err != nil {
		return nil, err
	}
	c.CheckVer(CFG_VER, flagVerbose)

	return c, nil
}

func (c *Config) Save(path string) error {
	if flagVerbose {
		fmt.Fprintln(os.Stderr, "saving config to", path)
	}
	f, err := os.Create(path)
	if err != nil {
		return err
	}
	defer f.Close()
	j := json.NewEncoder(f)
	return j.Encode(c)
}
<|endoftext|>