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

// Package watchdog is responsible for monitoring the sentry for tasks that may
// potentially be stuck or looping inderterminally causing hard to debug hungs in
// the untrusted app.
//
// It works by periodically querying all tasks to check whether they are in user
// mode (RunUser), kernel mode (RunSys), or blocked in the kernel (OffCPU). Tasks
// that have been running in kernel mode for a long time in the same syscall
// without blocking are considered stuck and are reported.
//
// When a stuck task is detected, the watchdog can take one of the following actions:
//		1. LogWarning: Logs a warning message followed by a stack dump of all goroutines.
//			 If a tasks continues to be stuck, the message will repeat every minute, unless
//			 a new stuck task is detected
//		2. Panic: same as above, followed by panic()
//
package watchdog

import (
	"bytes"
	"fmt"
	"time"

	"gvisor.dev/gvisor/pkg/abi/linux"
	"gvisor.dev/gvisor/pkg/log"
	"gvisor.dev/gvisor/pkg/metric"
	"gvisor.dev/gvisor/pkg/sentry/kernel"
	ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time"
	"gvisor.dev/gvisor/pkg/sync"
)

// Opts configures the watchdog.
type Opts struct {
	// TaskTimeout is the amount of time to allow a task to execute the
	// same syscall without blocking before it's declared stuck.
	TaskTimeout time.Duration

	// TaskTimeoutAction indicates what action to take when a stuck tasks
	// is detected.
	TaskTimeoutAction Action

	// StartupTimeout is the amount of time to allow between watchdog
	// creation and calling watchdog.Start.
	StartupTimeout time.Duration

	// StartupTimeoutAction indicates what action to take when
	// watchdog.Start is not called within the timeout.
	StartupTimeoutAction Action
}

// DefaultOpts is a default set of options for the watchdog.
var DefaultOpts = Opts{
	// Task timeout.
	TaskTimeout:       3 * time.Minute,
	TaskTimeoutAction: LogWarning,

	// Startup timeout.
	StartupTimeout:       30 * time.Second,
	StartupTimeoutAction: LogWarning,
}

// descheduleThreshold is the amount of time scheduling needs to be off before the entire wait period
// is discounted from task's last update time. It's set high enough that small scheduling delays won't
// trigger it.
const descheduleThreshold = 1 * time.Second

var stuckTasks = metric.MustCreateNewUint64Metric("/watchdog/stuck_tasks_detected", true /* sync */, "Cumulative count of stuck tasks detected")

// Amount of time to wait before dumping the stack to the log again when the same task(s) remains stuck.
var stackDumpSameTaskPeriod = time.Minute

// Action defines what action to take when a stuck task is detected.
type Action int

const (
	// LogWarning logs warning message followed by stack trace.
	LogWarning Action = iota

	// Panic will do the same logging as LogWarning and panic().
	Panic
)

// String returns Action's string representation.
func (a Action) String() string {
	switch a {
	case LogWarning:
		return "LogWarning"
	case Panic:
		return "Panic"
	default:
		panic(fmt.Sprintf("Invalid action: %d", a))
	}
}

// Watchdog is the main watchdog class. It controls a goroutine that periodically
// analyses all tasks and reports if any of them appear to be stuck.
type Watchdog struct {
	// Configuration options are embedded.
	Opts

	// period indicates how often to check all tasks. It's calculated based on
	// opts.TaskTimeout.
	period time.Duration

	// k is where the tasks come from.
	k *kernel.Kernel

	// stop is used to notify to watchdog should stop.
	stop chan struct{}

	// done is used to notify when the watchdog has stopped.
	done chan struct{}

	// offenders map contains all tasks that are currently stuck.
	offenders map[*kernel.Task]*offender

	// lastStackDump tracks the last time a stack dump was generated to prevent
	// spamming the log.
	lastStackDump time.Time

	// lastRun is set to the last time the watchdog executed a monitoring loop.
	lastRun ktime.Time

	// mu protects the fields below.
	mu sync.Mutex

	// running is true if the watchdog is running.
	running bool

	// startCalled is true if Start has ever been called. It remains true
	// even if Stop is called.
	startCalled bool
}

type offender struct {
	lastUpdateTime ktime.Time
}

// New creates a new watchdog.
func New(k *kernel.Kernel, opts Opts) *Watchdog {
	// 4 is arbitrary, just don't want to prolong 'TaskTimeout' too much.
	period := opts.TaskTimeout / 4
	w := &Watchdog{
		Opts:      opts,
		k:         k,
		period:    period,
		offenders: make(map[*kernel.Task]*offender),
		stop:      make(chan struct{}),
		done:      make(chan struct{}),
	}

	// Handle StartupTimeout if it exists.
	if w.StartupTimeout > 0 {
		log.Infof("Watchdog waiting %v for startup", w.StartupTimeout)
		go w.waitForStart() // S/R-SAFE: watchdog is stopped buring save and restarted after restore.
	}

	return w
}

// Start starts the watchdog.
func (w *Watchdog) Start() {
	w.mu.Lock()
	defer w.mu.Unlock()
	w.startCalled = true

	if w.running {
		return
	}

	if w.TaskTimeout == 0 {
		log.Infof("Watchdog task timeout disabled")
		return
	}
	w.lastRun = w.k.MonotonicClock().Now()

	log.Infof("Starting watchdog, period: %v, timeout: %v, action: %v", w.period, w.TaskTimeout, w.TaskTimeoutAction)
	go w.loop() // S/R-SAFE: watchdog is stopped during save and restarted after restore.
	w.running = true
}

// Stop requests the watchdog to stop and wait for it.
func (w *Watchdog) Stop() {
	if w.TaskTimeout == 0 {
		return
	}

	w.mu.Lock()
	defer w.mu.Unlock()
	if !w.running {
		return
	}
	log.Infof("Stopping watchdog")
	w.stop <- struct{}{}
	<-w.done
	w.running = false
	log.Infof("Watchdog stopped")
}

// waitForStart waits for Start to be called and takes action if it does not
// happen within the startup timeout.
func (w *Watchdog) waitForStart() {
	<-time.After(w.StartupTimeout)
	w.mu.Lock()
	defer w.mu.Unlock()
	if w.startCalled {
		// We are fine.
		return
	}
	var buf bytes.Buffer
	buf.WriteString(fmt.Sprintf("Watchdog.Start() not called within %s", w.StartupTimeout))
	w.doAction(w.StartupTimeoutAction, false, &buf)
}

// loop is the main watchdog routine. It only returns when 'Stop()' is called.
func (w *Watchdog) loop() {
	// Loop until someone stops it.
	for {
		select {
		case <-w.stop:
			w.done <- struct{}{}
			return
		case <-time.After(w.period):
			w.runTurn()
		}
	}
}

// runTurn runs a single pass over all tasks and reports anything it finds.
func (w *Watchdog) runTurn() {
	// Someone needs to watch the watchdog. The call below can get stuck if there
	// is a deadlock affecting root's PID namespace mutex. Run it in a goroutine
	// and report if it takes too long to return.
	var tasks []*kernel.Task
	done := make(chan struct{})
	go func() { // S/R-SAFE: watchdog is stopped and restarted during S/R.
		tasks = w.k.TaskSet().Root.Tasks()
		close(done)
	}()

	select {
	case <-done:
	case <-time.After(w.TaskTimeout):
		// Report if the watchdog is not making progress.
		// No one is wathching the watchdog watcher though.
		w.reportStuckWatchdog()
		<-done
	}

	newOffenders := make(map[*kernel.Task]*offender)
	newTaskFound := false
	now := ktime.FromNanoseconds(int64(w.k.CPUClockNow() * uint64(linux.ClockTick)))

	// The process may be running with low CPU limit making tasks appear stuck because
	// are starved of CPU cycles. An estimate is that Tasks could have been starved
	// since the last time the watchdog run. If the watchdog detects that scheduling
	// is off, it will discount the entire duration since last run from 'lastUpdateTime'.
	discount := time.Duration(0)
	if now.Sub(w.lastRun.Add(w.period)) > descheduleThreshold {
		discount = now.Sub(w.lastRun)
	}
	w.lastRun = now

	log.Infof("Watchdog starting loop, tasks: %d, discount: %v", len(tasks), discount)
	for _, t := range tasks {
		tsched := t.TaskGoroutineSchedInfo()

		// An offender is a task running inside the kernel for longer than the specified timeout.
		if tsched.State == kernel.TaskGoroutineRunningSys {
			lastUpdateTime := ktime.FromNanoseconds(int64(tsched.Timestamp * uint64(linux.ClockTick)))
			elapsed := now.Sub(lastUpdateTime) - discount
			if elapsed > w.TaskTimeout {
				tc, ok := w.offenders[t]
				if !ok {
					// New stuck task detected.
					//
					// Note that tasks blocked doing IO may be considered stuck in kernel,
					// unless they are surrounded b
					// Task.UninterruptibleSleepStart/Finish.
					tc = &offender{lastUpdateTime: lastUpdateTime}
					stuckTasks.Increment()
					newTaskFound = true
				}
				newOffenders[t] = tc
			}
		}
	}
	if len(newOffenders) > 0 {
		w.report(newOffenders, newTaskFound, now)
	}

	// Remember which tasks have been reported.
	w.offenders = newOffenders
}

// report takes appropriate action when a stuck task is detected.
func (w *Watchdog) report(offenders map[*kernel.Task]*offender, newTaskFound bool, now ktime.Time) {
	var buf bytes.Buffer
	buf.WriteString(fmt.Sprintf("Sentry detected %d stuck task(s):\n", len(offenders)))
	for t, o := range offenders {
		tid := w.k.TaskSet().Root.IDOfTask(t)
		buf.WriteString(fmt.Sprintf("\tTask tid: %v (%#x), entered RunSys state %v ago.\n", tid, uint64(tid), now.Sub(o.lastUpdateTime)))
	}

	buf.WriteString("Search for '(*Task).run(0x..., 0x<tid>)' in the stack dump to find the offending goroutine")

	// Dump stack only if a new task is detected or if it sometime has
	// passed since the last time a stack dump was generated.
	skipStack := newTaskFound || time.Since(w.lastStackDump) >= stackDumpSameTaskPeriod
	w.doAction(w.TaskTimeoutAction, skipStack, &buf)
}

func (w *Watchdog) reportStuckWatchdog() {
	var buf bytes.Buffer
	buf.WriteString("Watchdog goroutine is stuck:")
	w.doAction(w.TaskTimeoutAction, false, &buf)
}

// doAction will take the given action. If the action is LogWarnind and
// skipStack is true, then the stack printing will be skipped.
func (w *Watchdog) doAction(action Action, skipStack bool, msg *bytes.Buffer) {
	switch action {
	case LogWarning:
		if skipStack {
			msg.WriteString("\n...[stack dump skipped]...")
			log.Warningf(msg.String())
			return

		}
		log.TracebackAll(msg.String())
		w.lastStackDump = time.Now()

	case Panic:
		// Panic will skip over running tasks, which is likely the culprit here. So manually
		// dump all stacks before panic'ing.
		log.TracebackAll(msg.String())

		// Attempt to flush metrics, timeout and move on in case metrics are stuck as well.
		metricsEmitted := make(chan struct{}, 1)
		go func() { // S/R-SAFE: watchdog is stopped during save and restarted after restore.
			// Flush metrics before killing process.
			metric.EmitMetricUpdate()
			metricsEmitted <- struct{}{}
		}()
		select {
		case <-metricsEmitted:
		case <-time.After(1 * time.Second):
		}
		panic(fmt.Sprintf("%s\nStack for running G's are skipped while panicking.", msg.String()))
	default:
		panic(fmt.Sprintf("Unknown watchdog action %v", action))

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

// Package watchdog is responsible for monitoring the sentry for tasks that may
// potentially be stuck or looping inderterminally causing hard to debug hungs in
// the untrusted app.
//
// It works by periodically querying all tasks to check whether they are in user
// mode (RunUser), kernel mode (RunSys), or blocked in the kernel (OffCPU). Tasks
// that have been running in kernel mode for a long time in the same syscall
// without blocking are considered stuck and are reported.
//
// When a stuck task is detected, the watchdog can take one of the following actions:
//		1. LogWarning: Logs a warning message followed by a stack dump of all goroutines.
//			 If a tasks continues to be stuck, the message will repeat every minute, unless
//			 a new stuck task is detected
//		2. Panic: same as above, followed by panic()
//
package watchdog

import (
	"bytes"
	"fmt"
	"time"

	"gvisor.dev/gvisor/pkg/abi/linux"
	"gvisor.dev/gvisor/pkg/log"
	"gvisor.dev/gvisor/pkg/metric"
	"gvisor.dev/gvisor/pkg/sentry/kernel"
	ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time"
	"gvisor.dev/gvisor/pkg/sync"
)

// Opts configures the watchdog.
type Opts struct {
	// TaskTimeout is the amount of time to allow a task to execute the
	// same syscall without blocking before it's declared stuck.
	TaskTimeout time.Duration

	// TaskTimeoutAction indicates what action to take when a stuck tasks
	// is detected.
	TaskTimeoutAction Action

	// StartupTimeout is the amount of time to allow between watchdog
	// creation and calling watchdog.Start.
	StartupTimeout time.Duration

	// StartupTimeoutAction indicates what action to take when
	// watchdog.Start is not called within the timeout.
	StartupTimeoutAction Action
}

// DefaultOpts is a default set of options for the watchdog.
var DefaultOpts = Opts{
	// Task timeout.
	TaskTimeout:       3 * time.Minute,
	TaskTimeoutAction: LogWarning,

	// Startup timeout.
	StartupTimeout:       30 * time.Second,
	StartupTimeoutAction: LogWarning,
}

// descheduleThreshold is the amount of time scheduling needs to be off before the entire wait period
// is discounted from task's last update time. It's set high enough that small scheduling delays won't
// trigger it.
const descheduleThreshold = 1 * time.Second

var stuckTasks = metric.MustCreateNewUint64Metric("/watchdog/stuck_tasks_detected", true /* sync */, "Cumulative count of stuck tasks detected")

// Amount of time to wait before dumping the stack to the log again when the same task(s) remains stuck.
var stackDumpSameTaskPeriod = time.Minute

// Action defines what action to take when a stuck task is detected.
type Action int

const (
	// LogWarning logs warning message followed by stack trace.
	LogWarning Action = iota

	// Panic will do the same logging as LogWarning and panic().
	Panic
)

// String returns Action's string representation.
func (a Action) String() string {
	switch a {
	case LogWarning:
		return "LogWarning"
	case Panic:
		return "Panic"
	default:
		panic(fmt.Sprintf("Invalid action: %d", a))
	}
}

// Watchdog is the main watchdog class. It controls a goroutine that periodically
// analyses all tasks and reports if any of them appear to be stuck.
type Watchdog struct {
	// Configuration options are embedded.
	Opts

	// period indicates how often to check all tasks. It's calculated based on
	// opts.TaskTimeout.
	period time.Duration

	// k is where the tasks come from.
	k *kernel.Kernel

	// stop is used to notify to watchdog should stop.
	stop chan struct{}

	// done is used to notify when the watchdog has stopped.
	done chan struct{}

	// offenders map contains all tasks that are currently stuck.
	offenders map[*kernel.Task]*offender

	// lastStackDump tracks the last time a stack dump was generated to prevent
	// spamming the log.
	lastStackDump time.Time

	// lastRun is set to the last time the watchdog executed a monitoring loop.
	lastRun ktime.Time

	// mu protects the fields below.
	mu sync.Mutex

	// running is true if the watchdog is running.
	running bool

	// startCalled is true if Start has ever been called. It remains true
	// even if Stop is called.
	startCalled bool
}

type offender struct {
	lastUpdateTime ktime.Time
}

// New creates a new watchdog.
func New(k *kernel.Kernel, opts Opts) *Watchdog {
	// 4 is arbitrary, just don't want to prolong 'TaskTimeout' too much.
	period := opts.TaskTimeout / 4
	w := &Watchdog{
		Opts:      opts,
		k:         k,
		period:    period,
		offenders: make(map[*kernel.Task]*offender),
		stop:      make(chan struct{}),
		done:      make(chan struct{}),
	}

	// Handle StartupTimeout if it exists.
	if w.StartupTimeout > 0 {
		log.Infof("Watchdog waiting %v for startup", w.StartupTimeout)
		go w.waitForStart() // S/R-SAFE: watchdog is stopped buring save and restarted after restore.
	}

	return w
}

// Start starts the watchdog.
func (w *Watchdog) Start() {
	w.mu.Lock()
	defer w.mu.Unlock()
	w.startCalled = true

	if w.running {
		return
	}

	if w.TaskTimeout == 0 {
		log.Infof("Watchdog task timeout disabled")
		return
	}
	w.lastRun = w.k.MonotonicClock().Now()

	log.Infof("Starting watchdog, period: %v, timeout: %v, action: %v", w.period, w.TaskTimeout, w.TaskTimeoutAction)
	go w.loop() // S/R-SAFE: watchdog is stopped during save and restarted after restore.
	w.running = true
}

// Stop requests the watchdog to stop and wait for it.
func (w *Watchdog) Stop() {
	if w.TaskTimeout == 0 {
		return
	}

	w.mu.Lock()
	defer w.mu.Unlock()
	if !w.running {
		return
	}
	log.Infof("Stopping watchdog")
	w.stop <- struct{}{}
	<-w.done
	w.running = false
	log.Infof("Watchdog stopped")
}

// waitForStart waits for Start to be called and takes action if it does not
// happen within the startup timeout.
func (w *Watchdog) waitForStart() {
	<-time.After(w.StartupTimeout)
	w.mu.Lock()
	defer w.mu.Unlock()
	if w.startCalled {
		// We are fine.
		return
	}
	var buf bytes.Buffer
	buf.WriteString(fmt.Sprintf("Watchdog.Start() not called within %s", w.StartupTimeout))
	w.doAction(w.StartupTimeoutAction, false, &buf)
}

// loop is the main watchdog routine. It only returns when 'Stop()' is called.
func (w *Watchdog) loop() {
	// Loop until someone stops it.
	for {
		select {
		case <-w.stop:
			w.done <- struct{}{}
			return
		case <-time.After(w.period):
			w.runTurn()
		}
	}
}

// runTurn runs a single pass over all tasks and reports anything it finds.
func (w *Watchdog) runTurn() {
	// Someone needs to watch the watchdog. The call below can get stuck if there
	// is a deadlock affecting root's PID namespace mutex. Run it in a goroutine
	// and report if it takes too long to return.
	var tasks []*kernel.Task
	done := make(chan struct{})
	go func() { // S/R-SAFE: watchdog is stopped and restarted during S/R.
		tasks = w.k.TaskSet().Root.Tasks()
		close(done)
	}()

	select {
	case <-done:
	case <-time.After(w.TaskTimeout):
		// Report if the watchdog is not making progress.
		// No one is wathching the watchdog watcher though.
		w.reportStuckWatchdog()
		<-done
	}

	newOffenders := make(map[*kernel.Task]*offender)
	newTaskFound := false
	now := ktime.FromNanoseconds(int64(w.k.CPUClockNow() * uint64(linux.ClockTick)))

	// The process may be running with low CPU limit making tasks appear stuck because
	// are starved of CPU cycles. An estimate is that Tasks could have been starved
	// since the last time the watchdog run. If the watchdog detects that scheduling
	// is off, it will discount the entire duration since last run from 'lastUpdateTime'.
	discount := time.Duration(0)
	if now.Sub(w.lastRun.Add(w.period)) > descheduleThreshold {
		discount = now.Sub(w.lastRun)
	}
	w.lastRun = now

	log.Infof("Watchdog starting loop, tasks: %d, discount: %v", len(tasks), discount)
	for _, t := range tasks {
		tsched := t.TaskGoroutineSchedInfo()

		// An offender is a task running inside the kernel for longer than the specified timeout.
		if tsched.State == kernel.TaskGoroutineRunningSys {
			lastUpdateTime := ktime.FromNanoseconds(int64(tsched.Timestamp * uint64(linux.ClockTick)))
			elapsed := now.Sub(lastUpdateTime) - discount
			if elapsed > w.TaskTimeout {
				tc, ok := w.offenders[t]
				if !ok {
					// New stuck task detected.
					//
					// Note that tasks blocked doing IO may be considered stuck in kernel,
					// unless they are surrounded b
					// Task.UninterruptibleSleepStart/Finish.
					tc = &offender{lastUpdateTime: lastUpdateTime}
					stuckTasks.Increment()
					newTaskFound = true
				}
				newOffenders[t] = tc
			}
		}
	}
	if len(newOffenders) > 0 {
		w.report(newOffenders, newTaskFound, now)
	}

	// Remember which tasks have been reported.
	w.offenders = newOffenders
}

// report takes appropriate action when a stuck task is detected.
func (w *Watchdog) report(offenders map[*kernel.Task]*offender, newTaskFound bool, now ktime.Time) {
	var buf bytes.Buffer
	buf.WriteString(fmt.Sprintf("Sentry detected %d stuck task(s):\n", len(offenders)))
	for t, o := range offenders {
		tid := w.k.TaskSet().Root.IDOfTask(t)
		buf.WriteString(fmt.Sprintf("\tTask tid: %v (%#x), entered RunSys state %v ago.\n", tid, uint64(tid), now.Sub(o.lastUpdateTime)))
	}

	buf.WriteString("Search for '(*Task).run(0x..., 0x<tid>)' in the stack dump to find the offending goroutine")

	// Dump stack only if a new task is detected or if it sometime has
	// passed since the last time a stack dump was generated.
	showStack := newTaskFound || time.Since(w.lastStackDump) >= stackDumpSameTaskPeriod
	w.doAction(w.TaskTimeoutAction, showStack, &buf)
}

func (w *Watchdog) reportStuckWatchdog() {
	var buf bytes.Buffer
	buf.WriteString("Watchdog goroutine is stuck:")
	w.doAction(w.TaskTimeoutAction, false, &buf)
}

// doAction will take the given action. If the action is LogWarning and
// showStack is false, then the stack printing will be skipped.
func (w *Watchdog) doAction(action Action, showStack bool, msg *bytes.Buffer) {
	switch action {
	case LogWarning:
		if !showStack {
			msg.WriteString("\n...[stack dump skipped]...")
			log.Warningf(msg.String())
			return
		}
		log.TracebackAll(msg.String())
		w.lastStackDump = time.Now()

	case Panic:
		// Panic will skip over running tasks, which is likely the culprit here. So manually
		// dump all stacks before panic'ing.
		log.TracebackAll(msg.String())

		// Attempt to flush metrics, timeout and move on in case metrics are stuck as well.
		metricsEmitted := make(chan struct{}, 1)
		go func() { // S/R-SAFE: watchdog is stopped during save and restarted after restore.
			// Flush metrics before killing process.
			metric.EmitMetricUpdate()
			metricsEmitted <- struct{}{}
		}()
		select {
		case <-metricsEmitted:
		case <-time.After(1 * time.Second):
		}
		panic(fmt.Sprintf("%s\nStack for running G's are skipped while panicking.", msg.String()))
	default:
		panic(fmt.Sprintf("Unknown watchdog action %v", action))

	}
}
<|endoftext|>


<commit_before>package modd

import (
	"bufio"
	"io"
	"os"
	"os/exec"

	"github.com/cortesi/termlog"
	"github.com/fatih/color"
)

func getShell() string {
	sh := os.Getenv("SHELL")
	if sh == "" {
		if _, err := os.Stat("/bin/sh"); err == nil {
			sh = "/bin/sh"
		}
	}
	return sh
}

func logOutput(fp io.ReadCloser, out func(string, ...interface{})) {
	r := bufio.NewReader(fp)
	for {
		line, _, err := r.ReadLine()
		if err != nil {
			return
		}
		out(string(line))
	}
}

// RunProc runs a process to completion, sending output to log
func RunProc(cmd string, log termlog.Logger) error {
	log.Say("%s %s", color.BlueString("prep:"), cmd)
	sh := getShell()
	c := exec.Command(sh, "-c", cmd)
	stdo, err := c.StdoutPipe()
	if err != nil {
		return err
	}
	stde, err := c.StderrPipe()
	if err != nil {
		return err
	}
	go logOutput(stde, log.Warn)
	go logOutput(stdo, log.Say)
	err = c.Start()
	if err != nil {
		return err
	}
	err = c.Wait()
	if err != nil {
		log.Shout("%s", c.ProcessState.String())
		return err
	}
	// FIXME: rusage stats here
	log.NoticeAs("cmdstats", "run time: %s", c.ProcessState.UserTime())
	return nil
}

// RunProcs runs all commands in sequence. Stops if any command returns an error.
func RunProcs(cmds []string, log termlog.Logger) error {
	for _, cmd := range cmds {
		err := RunProc(cmd, log)
		if err != nil {
			return err
		}
	}
	return nil
}

type daemon struct{}

// DaemonPen is a group of daemons, managed as a unit.
type DaemonPen struct {
	daemons *[]daemon
}

// Start starts set of daemons, each specified by a command
func (dp *DaemonPen) Start(commands []string) {

}
<commit_msg>Stub out daemon management<commit_after>package modd

import (
	"bufio"
	"io"
	"os"
	"os/exec"

	"github.com/cortesi/termlog"
	"github.com/fatih/color"
)

func getShell() string {
	sh := os.Getenv("SHELL")
	if sh == "" {
		if _, err := os.Stat("/bin/sh"); err == nil {
			sh = "/bin/sh"
		}
	}
	return sh
}

func logOutput(fp io.ReadCloser, out func(string, ...interface{})) {
	r := bufio.NewReader(fp)
	for {
		line, _, err := r.ReadLine()
		if err != nil {
			return
		}
		out(string(line))
	}
}

// RunProc runs a process to completion, sending output to log
func RunProc(cmd string, log termlog.Logger) error {
	log.Say("%s %s", color.BlueString("prep:"), cmd)
	sh := getShell()
	c := exec.Command(sh, "-c", cmd)
	stdo, err := c.StdoutPipe()
	if err != nil {
		return err
	}
	stde, err := c.StderrPipe()
	if err != nil {
		return err
	}
	go logOutput(stde, log.Warn)
	go logOutput(stdo, log.Say)
	err = c.Start()
	if err != nil {
		return err
	}
	err = c.Wait()
	if err != nil {
		log.Shout("%s", c.ProcessState.String())
		return err
	}
	// FIXME: rusage stats here
	log.NoticeAs("cmdstats", "run time: %s", c.ProcessState.UserTime())
	return nil
}

// RunProcs runs all commands in sequence. Stops if any command returns an error.
func RunProcs(cmds []string, log termlog.Logger) error {
	for _, cmd := range cmds {
		err := RunProc(cmd, log)
		if err != nil {
			return err
		}
	}
	return nil
}

type daemon struct {
	cmd string
	log termlog.Logger
}

func (d *daemon) Start() error {
	d.log.Say("%s %s", color.BlueString("daemon:"), d.cmd)
	sh := getShell()
	c := exec.Command(sh, "-c", d.cmd)
	stdo, err := c.StdoutPipe()
	if err != nil {
		return err
	}
	stde, err := c.StderrPipe()
	if err != nil {
		return err
	}
	go logOutput(stde, d.log.Warn)
	go logOutput(stdo, d.log.Say)
	err = c.Start()
	if err != nil {
		return err
	}
	err = c.Wait()
	if err != nil {
		d.log.Shout("%s", c.ProcessState.String())
		return err
	}
	// FIXME: rusage stats here
	d.log.NoticeAs("cmdstats", "run time: %s", c.ProcessState.UserTime())
	return nil

}

func (d *daemon) Restart() {

}

// DaemonPen is a group of daemons, managed as a unit.
type DaemonPen struct {
	daemons *[]daemon
}

// Start starts set of daemons, each specified by a command
func (dp *DaemonPen) Start(commands []string, log termlog.Logger) {
	d := make([]daemon, len(commands))
	for i, c := range commands {
		d[i] = daemon{
			cmd: c,
			log: log,
		}
		d[i].Start()
	}
}
<|endoftext|>


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

// Bee is a tool for developling applications based on beego framework.
package main

import (
	"flag"
	"fmt"
	"html/template"
	"io"
	"log"
	"os"
	"strings"
)

const version = "1.0.1"

type Command struct {
	// Run runs the command.
	// The args are the arguments after the command name.
	Run func(cmd *Command, args []string)

	// UsageLine is the one-line usage message.
	// The first word in the line is taken to be the command name.
	UsageLine string

	// Short is the short description shown in the 'go help' output.
	Short string

	// Long is the long message shown in the 'go help <this-command>' output.
	Long string

	// Flag is a set of flags specific to this command.
	Flag flag.FlagSet

	// CustomFlags indicates that the command will do its own
	// flag parsing.
	CustomFlags bool
}

// Name returns the command's name: the first word in the usage line.
func (c *Command) Name() string {
	name := c.UsageLine
	i := strings.Index(name, " ")
	if i >= 0 {
		name = name[:i]
	}
	return name
}

func (c *Command) Usage() {
	fmt.Fprintf(os.Stderr, "usage: %s\n\n", c.UsageLine)
	fmt.Fprintf(os.Stderr, "%s\n", strings.TrimSpace(c.Long))
	os.Exit(2)
}

// Runnable reports whether the command can be run; otherwise
// it is a documentation pseudo-command such as importpath.
func (c *Command) Runnable() bool {
	return c.Run != nil
}

var commands = []*Command{
	cmdNew,
	cmdRun,
	cmdPack,
	cmdApiapp,
	cmdRouter,
	cmdTest,
	cmdBale,
	cmdVersion,
	//cmdReStart,
}

func main() {
	flag.Usage = usage
	flag.Parse()
	log.SetFlags(0)

	args := flag.Args()
	if len(args) < 1 {
		usage()
	}

	if args[0] == "help" {
		help(args[1:])
		return
	}

	for _, cmd := range commands {
		if cmd.Name() == args[0] && cmd.Run != nil {
			cmd.Flag.Usage = func() { cmd.Usage() }
			if cmd.CustomFlags {
				args = args[1:]
			} else {
				cmd.Flag.Parse(args[1:])
				args = cmd.Flag.Args()
			}
			cmd.Run(cmd, args)
			os.Exit(2)
			return
		}
	}

	fmt.Fprintf(os.Stderr, "bee: unknown subcommand %q\nRun 'bee help' for usage.\n", args[0])
	os.Exit(2)
}

var usageTemplate = `Bee is a tool for managing beego framework.

Usage:

	bee command [arguments]

The commands are:
{{range .}}{{if .Runnable}}
    {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}}

Use "bee help [command]" for more information about a command.

Additional help topics:
{{range .}}{{if not .Runnable}}
    {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}}

Use "bee help [topic]" for more information about that topic.

`

var helpTemplate = `{{if .Runnable}}usage: bee {{.UsageLine}}

{{end}}{{.Long | trim}}
`

func usage() {
	tmpl(os.Stdout, usageTemplate, commands)
	os.Exit(2)
}

func tmpl(w io.Writer, text string, data interface{}) {
	t := template.New("top")
	t.Funcs(template.FuncMap{"trim": strings.TrimSpace})
	template.Must(t.Parse(text))
	if err := t.Execute(w, data); err != nil {
		panic(err)
	}
}

func help(args []string) {
	if len(args) == 0 {
		usage()
		// not exit 2: succeeded at 'go help'.
		return
	}
	if len(args) != 1 {
		fmt.Fprintf(os.Stdout, "usage: bee help command\n\nToo many arguments given.\n")
		os.Exit(2) // failed at 'bee help'
	}

	arg := args[0]

	for _, cmd := range commands {
		if cmd.Name() == arg {
			tmpl(os.Stdout, helpTemplate, cmd)
			// not exit 2: succeeded at 'go help cmd'.
			return
		}
	}

	fmt.Fprintf(os.Stdout, "Unknown help topic %#q.  Run 'bee help'.\n", arg)
	os.Exit(2) // failed at 'bee help cmd'
}
<commit_msg>don't convert '&' to '&' in `bee` and `bee help version`.<commit_after>// Copyright 2013 bee authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

// Bee is a tool for developling applications based on beego framework.
package main

import (
	"flag"
	"fmt"
	"html/template"
	"io"
	"log"
	"os"
	"strings"
)

const version = "1.0.1"

type Command struct {
	// Run runs the command.
	// The args are the arguments after the command name.
	Run func(cmd *Command, args []string)

	// UsageLine is the one-line usage message.
	// The first word in the line is taken to be the command name.
	UsageLine string

	// Short is the short description shown in the 'go help' output.
	Short template.HTML

	// Long is the long message shown in the 'go help <this-command>' output.
	Long template.HTML

	// Flag is a set of flags specific to this command.
	Flag flag.FlagSet

	// CustomFlags indicates that the command will do its own
	// flag parsing.
	CustomFlags bool
}

// Name returns the command's name: the first word in the usage line.
func (c *Command) Name() string {
	name := c.UsageLine
	i := strings.Index(name, " ")
	if i >= 0 {
		name = name[:i]
	}
	return name
}

func (c *Command) Usage() {
	fmt.Fprintf(os.Stderr, "usage: %s\n\n", c.UsageLine)
	fmt.Fprintf(os.Stderr, "%s\n", strings.TrimSpace(string(c.Long)))
	os.Exit(2)
}

// Runnable reports whether the command can be run; otherwise
// it is a documentation pseudo-command such as importpath.
func (c *Command) Runnable() bool {
	return c.Run != nil
}

var commands = []*Command{
	cmdNew,
	cmdRun,
	cmdPack,
	cmdApiapp,
	cmdRouter,
	cmdTest,
	cmdBale,
	cmdVersion,
	//cmdReStart,
}

func main() {
	flag.Usage = usage
	flag.Parse()
	log.SetFlags(0)

	args := flag.Args()
	if len(args) < 1 {
		usage()
	}

	if args[0] == "help" {
		help(args[1:])
		return
	}

	for _, cmd := range commands {
		if cmd.Name() == args[0] && cmd.Run != nil {
			cmd.Flag.Usage = func() { cmd.Usage() }
			if cmd.CustomFlags {
				args = args[1:]
			} else {
				cmd.Flag.Parse(args[1:])
				args = cmd.Flag.Args()
			}
			cmd.Run(cmd, args)
			os.Exit(2)
			return
		}
	}

	fmt.Fprintf(os.Stderr, "bee: unknown subcommand %q\nRun 'bee help' for usage.\n", args[0])
	os.Exit(2)
}

var usageTemplate = `Bee is a tool for managing beego framework.

Usage:

	bee command [arguments]

The commands are:
{{range .}}{{if .Runnable}}
    {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}}

Use "bee help [command]" for more information about a command.

Additional help topics:
{{range .}}{{if not .Runnable}}
    {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}}

Use "bee help [topic]" for more information about that topic.

`

var helpTemplate = `{{if .Runnable}}usage: bee {{.UsageLine}}

{{end}}{{.Long | trim}}
`

func usage() {
	tmpl(os.Stdout, usageTemplate, commands)
	os.Exit(2)
}

func tmpl(w io.Writer, text string, data interface{}) {
	t := template.New("top")
	t.Funcs(template.FuncMap{"trim": func(s template.HTML) template.HTML {
		return template.HTML(strings.TrimSpace(string(s)))
	}})
	template.Must(t.Parse(text))
	if err := t.Execute(w, data); err != nil {
		panic(err)
	}
}

func help(args []string) {
	if len(args) == 0 {
		usage()
		// not exit 2: succeeded at 'go help'.
		return
	}
	if len(args) != 1 {
		fmt.Fprintf(os.Stdout, "usage: bee help command\n\nToo many arguments given.\n")
		os.Exit(2) // failed at 'bee help'
	}

	arg := args[0]

	for _, cmd := range commands {
		if cmd.Name() == arg {
			tmpl(os.Stdout, helpTemplate, cmd)
			// not exit 2: succeeded at 'go help cmd'.
			return
		}
	}

	fmt.Fprintf(os.Stdout, "Unknown help topic %#q.  Run 'bee help'.\n", arg)
	os.Exit(2) // failed at 'bee help cmd'
}
<|endoftext|>


<commit_before>package netlify

import (
	"crypto/sha1"
	"encoding/hex"
	"errors"
	"io/ioutil"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/cenkalti/backoff"
)

const MaxFilesForSyncDeploy = 1000
const PreProcessingTimeout = time.Minute * 5

// Deploy represents a specific deploy of a site
type Deploy struct {
	Id     string `json:"id"`
	SiteId string `json:"site_id"`
	UserId string `json:"user_id"`

	// State of the deploy (uploading/uploaded/processing/ready/error)
	State string `json:"state"`

	// Cause of error if State is "error"
	ErrorMessage string `json:"error_message"`

	// Shas of files that needs to be uploaded before the deploy is ready
	Required []string `json:"required"`

	DeployUrl     string `json:"deploy_url"`
	SiteUrl       string `json:"url"`
	ScreenshotUrl string `json:"screenshot_url"`

	CreatedAt Timestamp `json:"created_at"`
	UpdatedAt Timestamp `json:"updated_at"`

	Branch    string `json:"branch"`
	CommitRef string `json:"commit_ref"`

	client *Client
	logger *logrus.Entry
}

func (d Deploy) log() *logrus.Entry {
	if d.logger == nil {
		d.logger = d.client.log.WithFields(logrus.Fields{
			"function": "deploy",
			"id":       d.Id,
			"site_id":  d.SiteId,
			"user_id":  d.UserId,
		})
	}

	return d.logger.WithField("state", d.State)
}

// DeploysService is used to access all Deploy related API methods
type DeploysService struct {
	site   *Site
	client *Client
}

type uploadError struct {
	err   error
	mutex *sync.Mutex
}

func (u *uploadError) Set(err error) {
	if err != nil {
		u.mutex.Lock()
		defer u.mutex.Unlock()
		if u.err != nil {
			u.err = err
		}
	}
}

func (u *uploadError) Get() error {
	u.mutex.Lock()
	defer u.mutex.Unlock()
	return err
}

type deployFiles struct {
	Files     *map[string]string `json:"files"`
	Async     bool               `json:"async"`
	Branch    string             `json:"branch"`
	CommitRef string             `json:"commit_ref"`
}

func (s *DeploysService) apiPath() string {
	if s.site != nil {
		return path.Join(s.site.apiPath(), "deploys")
	}
	return "/deploys"
}

// Create a new deploy
//
// Example: site.Deploys.Create("/path/to/site-dir", true)
// If the target is a zip file, it must have the extension .zip
func (s *DeploysService) Create(dirOrZip string) (*Deploy, *Response, error) {
	return s.create(dirOrZip, false)
}

// Create a new draft deploy. Draft deploys will be uploaded and processed, but
// won't affect the active deploy for a site.
func (s *DeploysService) CreateDraft(dirOrZip string) (*Deploy, *Response, error) {
	return s.create(dirOrZip, true)
}

func (s *DeploysService) create(dirOrZip string, draft bool) (*Deploy, *Response, error) {
	if s.site == nil {
		return nil, nil, errors.New("You can only create a new deploy for an existing site (site.Deploys.Create(dirOrZip)))")
	}

	params := url.Values{}
	if draft {
		params["draft"] = []string{"true"}
	}
	options := &RequestOptions{QueryParams: &params}
	deploy := &Deploy{client: s.client}
	resp, err := s.client.Request("POST", s.apiPath(), options, deploy)

	if err != nil {
		return deploy, resp, err
	}

	resp, err = deploy.Deploy(dirOrZip)
	return deploy, resp, err
}

// List all deploys. Takes ListOptions to control pagination.
func (s *DeploysService) List(options *ListOptions) ([]Deploy, *Response, error) {
	deploys := new([]Deploy)

	reqOptions := &RequestOptions{QueryParams: options.toQueryParamsMap()}

	resp, err := s.client.Request("GET", s.apiPath(), reqOptions, deploys)

	for _, deploy := range *deploys {
		deploy.client = s.client
	}

	return *deploys, resp, err
}

// Get a specific deploy.
func (d *DeploysService) Get(id string) (*Deploy, *Response, error) {
	deploy := &Deploy{Id: id, client: d.client}
	resp, err := deploy.Reload()

	return deploy, resp, err
}

func (deploy *Deploy) apiPath() string {
	return path.Join("/deploys", deploy.Id)
}

func (deploy *Deploy) Deploy(dirOrZip string) (*Response, error) {
	if strings.HasSuffix(dirOrZip, ".zip") {
		return deploy.deployZip(dirOrZip)
	} else {
		return deploy.deployDir(dirOrZip)
	}
}

// Reload a deploy from the API
func (deploy *Deploy) Reload() (*Response, error) {
	if deploy.Id == "" {
		return nil, errors.New("Cannot fetch deploy without an ID")
	}
	return deploy.client.Request("GET", deploy.apiPath(), nil, deploy)
}

// Restore an old deploy. Sets the deploy as the active deploy for a site
func (deploy *Deploy) Restore() (*Response, error) {
	return deploy.client.Request("POST", path.Join(deploy.apiPath(), "restore"), nil, deploy)
}

// Alias for restore. Published a specific deploy.
func (deploy *Deploy) Publish() (*Response, error) {
	return deploy.Restore()
}

func (deploy *Deploy) uploadFile(dir, path string, sharedError uploadError) error {
	if sharedError.Get() != nil {
		return errors.New("Canceled because upload has already failed")
	}

	log := deploy.log().WithFields(logrus.Fields{
		"dir":  dir,
		"path": path,
	})

	log.Debugf("Uploading file: %v", path)
	file, err := os.Open(filepath.Join(dir, path))
	defer file.Close()

	if err != nil {
		log.Warnf("Error opening file %v: %v", path, err)
		return err
	}

	info, err := file.Stat()

	if err != nil {
		log.Warnf("Error getting file size %v: %v", path, err)
		return err
	}

	options := &RequestOptions{
		RawBody:       file,
		RawBodyLength: info.Size(),
		Headers:       &map[string]string{"Content-Type": "application/octet-stream"},
	}

	fileUrl, err := url.Parse(path)
	if err != nil {
		log.Warnf("Error parsing url %v: %v", path, err)
		return err
	}

	resp, err := deploy.client.Request("PUT", filepath.Join(deploy.apiPath(), "files", fileUrl.Path), options, nil)
	if resp != nil && resp.Response != nil && resp.Body != nil {
		resp.Body.Close()
	}
	if err != nil {
		log.Warnf("Error while uploading %v: %v", path, err)
		return err
	}

	log.Debug("Finished uploading file")
	return err
}

// deployDir scans the given directory and deploys the files
// that have changed on Netlify.
func (deploy *Deploy) deployDir(dir string) (*Response, error) {
	return deploy.DeployDirWithGitInfo(dir, "", "")
}

// DeployDirWithGitInfo scans the given directory and deploys the files
// that have changed on Netlify.
//
// This function allows you to supply git information about the deploy
// when it hasn't been set previously be a Continuous Deployment process.
func (deploy *Deploy) DeployDirWithGitInfo(dir, branch, commitRef string) (*Response, error) {
	files := map[string]string{}
	log := deploy.log().WithFields(logrus.Fields{
		"dir":        dir,
		"branch":     branch,
		"commit_ref": commitRef,
	})
	defer log.Infof("Finished deploying directory %s", dir)

	log.Infof("Starting deploy of directory %s", dir)
	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if info.IsDir() == false && info.Mode().IsRegular() {
			rel, err := filepath.Rel(dir, path)
			if err != nil {
				return err
			}

			if ignoreFile(rel) {
				return nil
			}

			sha := sha1.New()
			data, err := ioutil.ReadFile(path)

			if err != nil {
				return err
			}

			sha.Write(data)

			files[rel] = hex.EncodeToString(sha.Sum(nil))
		}

		return nil
	})
	if err != nil {
		log.WithError(err).Warn("Failed to walk directory structure")
		return nil, err
	}

	fileOptions := &deployFiles{
		Files:     &files,
		Branch:    branch,
		CommitRef: commitRef,
	}

	if len(files) > MaxFilesForSyncDeploy {
		log.Debugf("More files than sync can deploy %d vs %d", len(files), MaxFilesForSyncDeploy)
		fileOptions.Async = true
	}

	options := &RequestOptions{
		JsonBody: fileOptions,
	}

	log.Debug("Starting to do PUT to origin")
	resp, err := deploy.client.Request("PUT", deploy.apiPath(), options, deploy)
	if err != nil {
		return resp, err
	}

	if len(files) > MaxFilesForSyncDeploy {
		start := time.Now()
		log.Debug("Starting to poll for the deploy to get into ready || prepared state")
		for {
			resp, err := deploy.client.Request("GET", deploy.apiPath(), nil, deploy)
			if err != nil {
				log.WithError(err).Warnf("Error fetching deploy, waiting for 5 seconds before retry: %v", err)
				time.Sleep(5 * time.Second)
			}
			resp.Body.Close()

			log.Debugf("Deploy state: %v\n", deploy.State)
			if deploy.State == "prepared" || deploy.State == "ready" {
				break
			}
			if deploy.State == "error" {
				log.Warnf("deploy is in state error")
				return resp, errors.New("Error: preprocessing deploy failed")
			}
			if start.Add(PreProcessingTimeout).Before(time.Now()) {
				log.Warnf("Deploy timed out waiting for preprocessing")
				return resp, errors.New("Error: preprocessing deploy timed out")
			}
			log.Debug("Waiting for 2 seconds to retry getting deploy")
			time.Sleep(2 * time.Second)
		}
	}

	lookup := map[string]bool{}

	for _, sha := range deploy.Required {
		lookup[sha] = true
	}

	log.Infof("Going to deploy the %d required files", len(lookup))

	// Use a channel as a semaphore to limit # of parallel uploads
	sem := make(chan int, deploy.client.MaxConcurrentUploads)
	var wg sync.WaitGroup

	sharedErr := uploadError{err: nil, mutex: &sync.Mutex{}}
	for path, sha := range files {
		if lookup[sha] == true && err == nil {
			sem <- 1
			go func(path string) {
				wg.Add(1)
				defer func() {
					<-sem
					wg.Done()
				}()
				log.Debugf("Starting to upload %s/%s", path, sha)
				if sharedErr.Get() != nil {
					return
				}

				b := backoff.NewExponentialBackOff()
				b.MaxElapsedTime = 2 * time.Minute
				err := backoff.Retry(func() error { return deploy.uploadFile(dir, path, sharedErr) }, b)
				if err != nil {
					sharedErr.Set(err)
				}
			}(path)
		}
	}

	log.Debugf("Waiting for required files to upload")
	wg.Wait()

	if sharedErr.Get() != nil {
		return resp, sharedErr.err
	}

	return resp, err
}

// deployZip uploads a Zip file to Netlify and deploys the files
// that have changed.
func (deploy *Deploy) deployZip(zip string) (*Response, error) {
	log := deploy.log().WithFields(logrus.Fields{
		"function": "zip",
		"zip_path": zip,
	})
	log.Infof("Starting to deploy zip file %s", zip)
	zipPath, err := filepath.Abs(zip)
	if err != nil {
		return nil, err
	}

	log.Debugf("Opening zip file at %s", zipPath)
	zipFile, err := os.Open(zipPath)
	if err != nil {
		return nil, err
	}
	defer zipFile.Close()

	info, err := zipFile.Stat()
	if err != nil {
		return nil, err
	}

	log.WithFields(logrus.Fields{
		"name": info.Name(),
		"size": info.Size(),
		"mode": info.Mode(),
	}).Debugf("Opened file %s of %s bytes", info.Name(), info.Size())

	options := &RequestOptions{
		RawBody:       zipFile,
		RawBodyLength: info.Size(),
		Headers:       &map[string]string{"Content-Type": "application/zip"},
	}

	log.Debug("Excuting PUT request for zip file")
	resp, err := deploy.client.Request("PUT", deploy.apiPath(), options, deploy)
	if err != nil {
		log.WithError(err).Warn("Error while uploading zip file")
	}

	log.Info("Finished uploading zip file")
	return resp, err
}

func (deploy *Deploy) WaitForReady(timeout time.Duration) error {
	if deploy.State == "ready" {
		return nil
	}

	if timeout == 0 {
		timeout = defaultTimeout
	}

	timedOut := false
	time.AfterFunc(timeout*time.Second, func() {
		timedOut = true
	})

	done := make(chan error)

	go func() {
		for {
			time.Sleep(1 * time.Second)

			if timedOut {
				done <- errors.New("Timeout while waiting for processing")
				break
			}

			_, err := deploy.Reload()
			if err != nil || (deploy.State == "ready") {
				done <- err
				break
			}
		}
	}()

	err := <-done
	return err
}

func ignoreFile(rel string) bool {
	if strings.HasPrefix(rel, ".") || strings.Contains(rel, "/.") || strings.HasPrefix(rel, "__MACOS") {
		if strings.HasPrefix(rel, ".well-known/") {
			return false
		}
		return true
	}
	return false
}
<commit_msg>fix minor mistake<commit_after>package netlify

import (
	"crypto/sha1"
	"encoding/hex"
	"errors"
	"io/ioutil"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/cenkalti/backoff"
)

const MaxFilesForSyncDeploy = 1000
const PreProcessingTimeout = time.Minute * 5

// Deploy represents a specific deploy of a site
type Deploy struct {
	Id     string `json:"id"`
	SiteId string `json:"site_id"`
	UserId string `json:"user_id"`

	// State of the deploy (uploading/uploaded/processing/ready/error)
	State string `json:"state"`

	// Cause of error if State is "error"
	ErrorMessage string `json:"error_message"`

	// Shas of files that needs to be uploaded before the deploy is ready
	Required []string `json:"required"`

	DeployUrl     string `json:"deploy_url"`
	SiteUrl       string `json:"url"`
	ScreenshotUrl string `json:"screenshot_url"`

	CreatedAt Timestamp `json:"created_at"`
	UpdatedAt Timestamp `json:"updated_at"`

	Branch    string `json:"branch"`
	CommitRef string `json:"commit_ref"`

	client *Client
	logger *logrus.Entry
}

func (d Deploy) log() *logrus.Entry {
	if d.logger == nil {
		d.logger = d.client.log.WithFields(logrus.Fields{
			"function": "deploy",
			"id":       d.Id,
			"site_id":  d.SiteId,
			"user_id":  d.UserId,
		})
	}

	return d.logger.WithField("state", d.State)
}

// DeploysService is used to access all Deploy related API methods
type DeploysService struct {
	site   *Site
	client *Client
}

type uploadError struct {
	err   error
	mutex *sync.Mutex
}

func (u *uploadError) Set(err error) {
	if err != nil {
		u.mutex.Lock()
		defer u.mutex.Unlock()
		if u.err != nil {
			u.err = err
		}
	}
}

func (u *uploadError) Get() error {
	u.mutex.Lock()
	defer u.mutex.Unlock()
	return u.err
}

type deployFiles struct {
	Files     *map[string]string `json:"files"`
	Async     bool               `json:"async"`
	Branch    string             `json:"branch"`
	CommitRef string             `json:"commit_ref"`
}

func (s *DeploysService) apiPath() string {
	if s.site != nil {
		return path.Join(s.site.apiPath(), "deploys")
	}
	return "/deploys"
}

// Create a new deploy
//
// Example: site.Deploys.Create("/path/to/site-dir", true)
// If the target is a zip file, it must have the extension .zip
func (s *DeploysService) Create(dirOrZip string) (*Deploy, *Response, error) {
	return s.create(dirOrZip, false)
}

// Create a new draft deploy. Draft deploys will be uploaded and processed, but
// won't affect the active deploy for a site.
func (s *DeploysService) CreateDraft(dirOrZip string) (*Deploy, *Response, error) {
	return s.create(dirOrZip, true)
}

func (s *DeploysService) create(dirOrZip string, draft bool) (*Deploy, *Response, error) {
	if s.site == nil {
		return nil, nil, errors.New("You can only create a new deploy for an existing site (site.Deploys.Create(dirOrZip)))")
	}

	params := url.Values{}
	if draft {
		params["draft"] = []string{"true"}
	}
	options := &RequestOptions{QueryParams: &params}
	deploy := &Deploy{client: s.client}
	resp, err := s.client.Request("POST", s.apiPath(), options, deploy)

	if err != nil {
		return deploy, resp, err
	}

	resp, err = deploy.Deploy(dirOrZip)
	return deploy, resp, err
}

// List all deploys. Takes ListOptions to control pagination.
func (s *DeploysService) List(options *ListOptions) ([]Deploy, *Response, error) {
	deploys := new([]Deploy)

	reqOptions := &RequestOptions{QueryParams: options.toQueryParamsMap()}

	resp, err := s.client.Request("GET", s.apiPath(), reqOptions, deploys)

	for _, deploy := range *deploys {
		deploy.client = s.client
	}

	return *deploys, resp, err
}

// Get a specific deploy.
func (d *DeploysService) Get(id string) (*Deploy, *Response, error) {
	deploy := &Deploy{Id: id, client: d.client}
	resp, err := deploy.Reload()

	return deploy, resp, err
}

func (deploy *Deploy) apiPath() string {
	return path.Join("/deploys", deploy.Id)
}

func (deploy *Deploy) Deploy(dirOrZip string) (*Response, error) {
	if strings.HasSuffix(dirOrZip, ".zip") {
		return deploy.deployZip(dirOrZip)
	} else {
		return deploy.deployDir(dirOrZip)
	}
}

// Reload a deploy from the API
func (deploy *Deploy) Reload() (*Response, error) {
	if deploy.Id == "" {
		return nil, errors.New("Cannot fetch deploy without an ID")
	}
	return deploy.client.Request("GET", deploy.apiPath(), nil, deploy)
}

// Restore an old deploy. Sets the deploy as the active deploy for a site
func (deploy *Deploy) Restore() (*Response, error) {
	return deploy.client.Request("POST", path.Join(deploy.apiPath(), "restore"), nil, deploy)
}

// Alias for restore. Published a specific deploy.
func (deploy *Deploy) Publish() (*Response, error) {
	return deploy.Restore()
}

func (deploy *Deploy) uploadFile(dir, path string, sharedError uploadError) error {
	if sharedError.Get() != nil {
		return errors.New("Canceled because upload has already failed")
	}

	log := deploy.log().WithFields(logrus.Fields{
		"dir":  dir,
		"path": path,
	})

	log.Debugf("Uploading file: %v", path)
	file, err := os.Open(filepath.Join(dir, path))
	defer file.Close()

	if err != nil {
		log.Warnf("Error opening file %v: %v", path, err)
		return err
	}

	info, err := file.Stat()

	if err != nil {
		log.Warnf("Error getting file size %v: %v", path, err)
		return err
	}

	options := &RequestOptions{
		RawBody:       file,
		RawBodyLength: info.Size(),
		Headers:       &map[string]string{"Content-Type": "application/octet-stream"},
	}

	fileUrl, err := url.Parse(path)
	if err != nil {
		log.Warnf("Error parsing url %v: %v", path, err)
		return err
	}

	resp, err := deploy.client.Request("PUT", filepath.Join(deploy.apiPath(), "files", fileUrl.Path), options, nil)
	if resp != nil && resp.Response != nil && resp.Body != nil {
		resp.Body.Close()
	}
	if err != nil {
		log.Warnf("Error while uploading %v: %v", path, err)
		return err
	}

	log.Debug("Finished uploading file")
	return err
}

// deployDir scans the given directory and deploys the files
// that have changed on Netlify.
func (deploy *Deploy) deployDir(dir string) (*Response, error) {
	return deploy.DeployDirWithGitInfo(dir, "", "")
}

// DeployDirWithGitInfo scans the given directory and deploys the files
// that have changed on Netlify.
//
// This function allows you to supply git information about the deploy
// when it hasn't been set previously be a Continuous Deployment process.
func (deploy *Deploy) DeployDirWithGitInfo(dir, branch, commitRef string) (*Response, error) {
	files := map[string]string{}
	log := deploy.log().WithFields(logrus.Fields{
		"dir":        dir,
		"branch":     branch,
		"commit_ref": commitRef,
	})
	defer log.Infof("Finished deploying directory %s", dir)

	log.Infof("Starting deploy of directory %s", dir)
	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if info.IsDir() == false && info.Mode().IsRegular() {
			rel, err := filepath.Rel(dir, path)
			if err != nil {
				return err
			}

			if ignoreFile(rel) {
				return nil
			}

			sha := sha1.New()
			data, err := ioutil.ReadFile(path)

			if err != nil {
				return err
			}

			sha.Write(data)

			files[rel] = hex.EncodeToString(sha.Sum(nil))
		}

		return nil
	})
	if err != nil {
		log.WithError(err).Warn("Failed to walk directory structure")
		return nil, err
	}

	fileOptions := &deployFiles{
		Files:     &files,
		Branch:    branch,
		CommitRef: commitRef,
	}

	if len(files) > MaxFilesForSyncDeploy {
		log.Debugf("More files than sync can deploy %d vs %d", len(files), MaxFilesForSyncDeploy)
		fileOptions.Async = true
	}

	options := &RequestOptions{
		JsonBody: fileOptions,
	}

	log.Debug("Starting to do PUT to origin")
	resp, err := deploy.client.Request("PUT", deploy.apiPath(), options, deploy)
	if err != nil {
		return resp, err
	}

	if len(files) > MaxFilesForSyncDeploy {
		start := time.Now()
		log.Debug("Starting to poll for the deploy to get into ready || prepared state")
		for {
			resp, err := deploy.client.Request("GET", deploy.apiPath(), nil, deploy)
			if err != nil {
				log.WithError(err).Warnf("Error fetching deploy, waiting for 5 seconds before retry: %v", err)
				time.Sleep(5 * time.Second)
			}
			resp.Body.Close()

			log.Debugf("Deploy state: %v\n", deploy.State)
			if deploy.State == "prepared" || deploy.State == "ready" {
				break
			}
			if deploy.State == "error" {
				log.Warnf("deploy is in state error")
				return resp, errors.New("Error: preprocessing deploy failed")
			}
			if start.Add(PreProcessingTimeout).Before(time.Now()) {
				log.Warnf("Deploy timed out waiting for preprocessing")
				return resp, errors.New("Error: preprocessing deploy timed out")
			}
			log.Debug("Waiting for 2 seconds to retry getting deploy")
			time.Sleep(2 * time.Second)
		}
	}

	lookup := map[string]bool{}

	for _, sha := range deploy.Required {
		lookup[sha] = true
	}

	log.Infof("Going to deploy the %d required files", len(lookup))

	// Use a channel as a semaphore to limit # of parallel uploads
	sem := make(chan int, deploy.client.MaxConcurrentUploads)
	var wg sync.WaitGroup

	sharedErr := uploadError{err: nil, mutex: &sync.Mutex{}}
	for path, sha := range files {
		if lookup[sha] == true && err == nil {
			sem <- 1
			go func(path string) {
				wg.Add(1)
				defer func() {
					<-sem
					wg.Done()
				}()
				log.Debugf("Starting to upload %s/%s", path, sha)
				if sharedErr.Get() != nil {
					return
				}

				b := backoff.NewExponentialBackOff()
				b.MaxElapsedTime = 2 * time.Minute
				err := backoff.Retry(func() error { return deploy.uploadFile(dir, path, sharedErr) }, b)
				if err != nil {
					sharedErr.Set(err)
				}
			}(path)
		}
	}

	log.Debugf("Waiting for required files to upload")
	wg.Wait()

	if sharedErr.Get() != nil {
		return resp, sharedErr.err
	}

	return resp, err
}

// deployZip uploads a Zip file to Netlify and deploys the files
// that have changed.
func (deploy *Deploy) deployZip(zip string) (*Response, error) {
	log := deploy.log().WithFields(logrus.Fields{
		"function": "zip",
		"zip_path": zip,
	})
	log.Infof("Starting to deploy zip file %s", zip)
	zipPath, err := filepath.Abs(zip)
	if err != nil {
		return nil, err
	}

	log.Debugf("Opening zip file at %s", zipPath)
	zipFile, err := os.Open(zipPath)
	if err != nil {
		return nil, err
	}
	defer zipFile.Close()

	info, err := zipFile.Stat()
	if err != nil {
		return nil, err
	}

	log.WithFields(logrus.Fields{
		"name": info.Name(),
		"size": info.Size(),
		"mode": info.Mode(),
	}).Debugf("Opened file %s of %s bytes", info.Name(), info.Size())

	options := &RequestOptions{
		RawBody:       zipFile,
		RawBodyLength: info.Size(),
		Headers:       &map[string]string{"Content-Type": "application/zip"},
	}

	log.Debug("Excuting PUT request for zip file")
	resp, err := deploy.client.Request("PUT", deploy.apiPath(), options, deploy)
	if err != nil {
		log.WithError(err).Warn("Error while uploading zip file")
	}

	log.Info("Finished uploading zip file")
	return resp, err
}

func (deploy *Deploy) WaitForReady(timeout time.Duration) error {
	if deploy.State == "ready" {
		return nil
	}

	if timeout == 0 {
		timeout = defaultTimeout
	}

	timedOut := false
	time.AfterFunc(timeout*time.Second, func() {
		timedOut = true
	})

	done := make(chan error)

	go func() {
		for {
			time.Sleep(1 * time.Second)

			if timedOut {
				done <- errors.New("Timeout while waiting for processing")
				break
			}

			_, err := deploy.Reload()
			if err != nil || (deploy.State == "ready") {
				done <- err
				break
			}
		}
	}()

	err := <-done
	return err
}

func ignoreFile(rel string) bool {
	if strings.HasPrefix(rel, ".") || strings.Contains(rel, "/.") || strings.HasPrefix(rel, "__MACOS") {
		if strings.HasPrefix(rel, ".well-known/") {
			return false
		}
		return true
	}
	return false
}
<|endoftext|>


<commit_before>package auth

import (
	"crypto/sha256"
	"encoding/hex"
	"net/http"
	"net/url"
	"time"

	"github.com/grafana/grafana/pkg/bus"
	"github.com/grafana/grafana/pkg/infra/serverlock"
	"github.com/grafana/grafana/pkg/log"
	"github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/registry"
	"github.com/grafana/grafana/pkg/services/sqlstore"
	"github.com/grafana/grafana/pkg/setting"
	"github.com/grafana/grafana/pkg/util"
)

func init() {
	registry.RegisterService(&UserAuthTokenServiceImpl{})
}

var (
	getTime          = time.Now
	UrgentRotateTime = 1 * time.Minute
	oneYearInSeconds = 31557600 //used as default maxage for session cookies. We validate/rotate them more often.
)

// UserAuthTokenService are used for generating and validating user auth tokens
type UserAuthTokenService interface {
	InitContextWithToken(ctx *models.ReqContext, orgID int64) bool
	UserAuthenticatedHook(user *models.User, c *models.ReqContext) error
	UserSignedOutHook(c *models.ReqContext)
}

type UserAuthTokenServiceImpl struct {
	SQLStore          *sqlstore.SqlStore            `inject:""`
	ServerLockService *serverlock.ServerLockService `inject:""`
	Cfg               *setting.Cfg                  `inject:""`
	log               log.Logger
}

// Init this service
func (s *UserAuthTokenServiceImpl) Init() error {
	s.log = log.New("auth")
	return nil
}

func (s *UserAuthTokenServiceImpl) InitContextWithToken(ctx *models.ReqContext, orgID int64) bool {
	//auth User
	unhashedToken := ctx.GetCookie(s.Cfg.LoginCookieName)
	if unhashedToken == "" {
		return false
	}

	userToken, err := s.LookupToken(unhashedToken)
	if err != nil {
		ctx.Logger.Info("failed to look up user based on cookie", "error", err)
		return false
	}

	query := models.GetSignedInUserQuery{UserId: userToken.UserId, OrgId: orgID}
	if err := bus.Dispatch(&query); err != nil {
		ctx.Logger.Error("Failed to get user with id", "userId", userToken.UserId, "error", err)
		return false
	}

	ctx.SignedInUser = query.Result
	ctx.IsSignedIn = true

	//rotate session token if needed.
	rotated, err := s.RefreshToken(userToken, ctx.RemoteAddr(), ctx.Req.UserAgent())
	if err != nil {
		ctx.Logger.Error("failed to rotate token", "error", err, "userId", userToken.UserId, "tokenId", userToken.Id)
		return true
	}

	if rotated {
		s.writeSessionCookie(ctx, userToken.UnhashedToken, oneYearInSeconds)
	}

	return true
}

func (s *UserAuthTokenServiceImpl) writeSessionCookie(ctx *models.ReqContext, value string, maxAge int) {
	if setting.Env == setting.DEV {
		ctx.Logger.Info("new token", "unhashed token", value)
	}

	ctx.Resp.Header().Del("Set-Cookie")
	cookie := http.Cookie{
		Name:     s.Cfg.LoginCookieName,
		Value:    url.QueryEscape(value),
		HttpOnly: true,
		Domain:   setting.Domain,
		Path:     setting.AppSubUrl + "/",
		Secure:   s.Cfg.SecurityHTTPSCookies,
		MaxAge:   maxAge,
	}

	http.SetCookie(ctx.Resp, &cookie)
}

func (s *UserAuthTokenServiceImpl) UserAuthenticatedHook(user *models.User, c *models.ReqContext) error {
	userToken, err := s.CreateToken(user.Id, c.RemoteAddr(), c.Req.UserAgent())
	if err != nil {
		return err
	}

	s.writeSessionCookie(c, userToken.UnhashedToken, oneYearInSeconds)
	return nil
}

func (s *UserAuthTokenServiceImpl) UserSignedOutHook(c *models.ReqContext) {
	s.writeSessionCookie(c, "", -1)
}

func (s *UserAuthTokenServiceImpl) CreateToken(userId int64, clientIP, userAgent string) (*userAuthToken, error) {
	clientIP = util.ParseIPAddress(clientIP)
	token, err := util.RandomHex(16)
	if err != nil {
		return nil, err
	}

	hashedToken := hashToken(token)

	now := getTime().Unix()

	userToken := userAuthToken{
		UserId:        userId,
		AuthToken:     hashedToken,
		PrevAuthToken: hashedToken,
		ClientIp:      clientIP,
		UserAgent:     userAgent,
		RotatedAt:     now,
		CreatedAt:     now,
		UpdatedAt:     now,
		SeenAt:        0,
		AuthTokenSeen: false,
	}
	_, err = s.SQLStore.NewSession().Insert(&userToken)
	if err != nil {
		return nil, err
	}

	userToken.UnhashedToken = token

	return &userToken, nil
}

func (s *UserAuthTokenServiceImpl) LookupToken(unhashedToken string) (*userAuthToken, error) {
	hashedToken := hashToken(unhashedToken)
	if setting.Env == setting.DEV {
		s.log.Info("looking up token", "unhashed", unhashedToken, "hashed", hashedToken)
	}

	expireBefore := getTime().Add(time.Duration(-86400*s.Cfg.LoginCookieMaxDays) * time.Second).Unix()

	var userToken userAuthToken
	exists, err := s.SQLStore.NewSession().Where("(auth_token = ? OR prev_auth_token = ?) AND created_at > ?", hashedToken, hashedToken, expireBefore).Get(&userToken)
	if err != nil {
		return nil, err
	}

	if !exists {
		return nil, ErrAuthTokenNotFound
	}

	if userToken.AuthToken != hashedToken && userToken.PrevAuthToken == hashedToken && userToken.AuthTokenSeen {
		userTokenCopy := userToken
		userTokenCopy.AuthTokenSeen = false
		expireBefore := getTime().Add(-UrgentRotateTime).Unix()
		affectedRows, err := s.SQLStore.NewSession().Where("id = ? AND prev_auth_token = ? AND rotated_at < ?", userTokenCopy.Id, userTokenCopy.PrevAuthToken, expireBefore).AllCols().Update(&userTokenCopy)
		if err != nil {
			return nil, err
		}

		if affectedRows == 0 {
			s.log.Debug("prev seen token unchanged", "userTokenId", userToken.Id, "userId", userToken.UserId, "authToken", userToken.AuthToken, "clientIP", userToken.ClientIp, "userAgent", userToken.UserAgent)
		} else {
			s.log.Debug("prev seen token", "userTokenId", userToken.Id, "userId", userToken.UserId, "authToken", userToken.AuthToken, "clientIP", userToken.ClientIp, "userAgent", userToken.UserAgent)
		}
	}

	if !userToken.AuthTokenSeen && userToken.AuthToken == hashedToken {
		userTokenCopy := userToken
		userTokenCopy.AuthTokenSeen = true
		userTokenCopy.SeenAt = getTime().Unix()
		affectedRows, err := s.SQLStore.NewSession().Where("id = ? AND auth_token = ?", userTokenCopy.Id, userTokenCopy.AuthToken).AllCols().Update(&userTokenCopy)
		if err != nil {
			return nil, err
		}

		if affectedRows == 1 {
			userToken = userTokenCopy
		}

		if affectedRows == 0 {
			s.log.Debug("seen wrong token", "userTokenId", userToken.Id, "userId", userToken.UserId, "authToken", userToken.AuthToken, "clientIP", userToken.ClientIp, "userAgent", userToken.UserAgent)
		} else {
			s.log.Debug("seen token", "userTokenId", userToken.Id, "userId", userToken.UserId, "authToken", userToken.AuthToken, "clientIP", userToken.ClientIp, "userAgent", userToken.UserAgent)
		}
	}

	userToken.UnhashedToken = unhashedToken

	return &userToken, nil
}

func (s *UserAuthTokenServiceImpl) RefreshToken(token *userAuthToken, clientIP, userAgent string) (bool, error) {
	if token == nil {
		return false, nil
	}

	now := getTime()

	needsRotation := false
	rotatedAt := time.Unix(token.RotatedAt, 0)
	if token.AuthTokenSeen {
		needsRotation = rotatedAt.Before(now.Add(-time.Duration(s.Cfg.LoginCookieRotation) * time.Minute))
	} else {
		needsRotation = rotatedAt.Before(now.Add(-UrgentRotateTime))
	}

	if !needsRotation {
		return false, nil
	}

	s.log.Debug("refresh token needs rotation?", "auth_token_seen", token.AuthTokenSeen, "rotated_at", rotatedAt, "token.Id", token.Id)

	clientIP = util.ParseIPAddress(clientIP)
	newToken, _ := util.RandomHex(16)
	hashedToken := hashToken(newToken)

	// very important that auth_token_seen is set after the prev_auth_token = case when ... for mysql to function correctly
	sql := `
		UPDATE user_auth_token
		SET
			seen_at = 0,
			user_agent = ?,
			client_ip = ?,
			prev_auth_token = case when auth_token_seen = ? then auth_token else prev_auth_token end,
			auth_token = ?,
			auth_token_seen = ?,
			rotated_at = ?
		WHERE id = ? AND (auth_token_seen = ? OR rotated_at < ?)`

	res, err := s.SQLStore.NewSession().Exec(sql, userAgent, clientIP, s.SQLStore.Dialect.BooleanStr(true), hashedToken, s.SQLStore.Dialect.BooleanStr(false), now.Unix(), token.Id, s.SQLStore.Dialect.BooleanStr(true), now.Add(-30*time.Second).Unix())
	if err != nil {
		return false, err
	}

	affected, _ := res.RowsAffected()
	s.log.Debug("rotated", "affected", affected, "auth_token_id", token.Id, "userId", token.UserId)
	if affected > 0 {
		token.UnhashedToken = newToken
		return true, nil
	}

	return false, nil
}

func hashToken(token string) string {
	hashBytes := sha256.Sum256([]byte(token + setting.SecretKey))
	return hex.EncodeToString(hashBytes[:])
}
<commit_msg>dont specify domain for auth cookies<commit_after>package auth

import (
	"crypto/sha256"
	"encoding/hex"
	"net/http"
	"net/url"
	"time"

	"github.com/grafana/grafana/pkg/bus"
	"github.com/grafana/grafana/pkg/infra/serverlock"
	"github.com/grafana/grafana/pkg/log"
	"github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/registry"
	"github.com/grafana/grafana/pkg/services/sqlstore"
	"github.com/grafana/grafana/pkg/setting"
	"github.com/grafana/grafana/pkg/util"
)

func init() {
	registry.RegisterService(&UserAuthTokenServiceImpl{})
}

var (
	getTime          = time.Now
	UrgentRotateTime = 1 * time.Minute
	oneYearInSeconds = 31557600 //used as default maxage for session cookies. We validate/rotate them more often.
)

// UserAuthTokenService are used for generating and validating user auth tokens
type UserAuthTokenService interface {
	InitContextWithToken(ctx *models.ReqContext, orgID int64) bool
	UserAuthenticatedHook(user *models.User, c *models.ReqContext) error
	UserSignedOutHook(c *models.ReqContext)
}

type UserAuthTokenServiceImpl struct {
	SQLStore          *sqlstore.SqlStore            `inject:""`
	ServerLockService *serverlock.ServerLockService `inject:""`
	Cfg               *setting.Cfg                  `inject:""`
	log               log.Logger
}

// Init this service
func (s *UserAuthTokenServiceImpl) Init() error {
	s.log = log.New("auth")
	return nil
}

func (s *UserAuthTokenServiceImpl) InitContextWithToken(ctx *models.ReqContext, orgID int64) bool {
	//auth User
	unhashedToken := ctx.GetCookie(s.Cfg.LoginCookieName)
	if unhashedToken == "" {
		return false
	}

	userToken, err := s.LookupToken(unhashedToken)
	if err != nil {
		ctx.Logger.Info("failed to look up user based on cookie", "error", err)
		return false
	}

	query := models.GetSignedInUserQuery{UserId: userToken.UserId, OrgId: orgID}
	if err := bus.Dispatch(&query); err != nil {
		ctx.Logger.Error("Failed to get user with id", "userId", userToken.UserId, "error", err)
		return false
	}

	ctx.SignedInUser = query.Result
	ctx.IsSignedIn = true

	//rotate session token if needed.
	rotated, err := s.RefreshToken(userToken, ctx.RemoteAddr(), ctx.Req.UserAgent())
	if err != nil {
		ctx.Logger.Error("failed to rotate token", "error", err, "userId", userToken.UserId, "tokenId", userToken.Id)
		return true
	}

	if rotated {
		s.writeSessionCookie(ctx, userToken.UnhashedToken, oneYearInSeconds)
	}

	return true
}

func (s *UserAuthTokenServiceImpl) writeSessionCookie(ctx *models.ReqContext, value string, maxAge int) {
	if setting.Env == setting.DEV {
		ctx.Logger.Info("new token", "unhashed token", value)
	}

	ctx.Resp.Header().Del("Set-Cookie")
	cookie := http.Cookie{
		Name:     s.Cfg.LoginCookieName,
		Value:    url.QueryEscape(value),
		HttpOnly: true,
		Path:     setting.AppSubUrl + "/",
		Secure:   s.Cfg.SecurityHTTPSCookies,
		MaxAge:   maxAge,
	}

	http.SetCookie(ctx.Resp, &cookie)
}

func (s *UserAuthTokenServiceImpl) UserAuthenticatedHook(user *models.User, c *models.ReqContext) error {
	userToken, err := s.CreateToken(user.Id, c.RemoteAddr(), c.Req.UserAgent())
	if err != nil {
		return err
	}

	s.writeSessionCookie(c, userToken.UnhashedToken, oneYearInSeconds)
	return nil
}

func (s *UserAuthTokenServiceImpl) UserSignedOutHook(c *models.ReqContext) {
	s.writeSessionCookie(c, "", -1)
}

func (s *UserAuthTokenServiceImpl) CreateToken(userId int64, clientIP, userAgent string) (*userAuthToken, error) {
	clientIP = util.ParseIPAddress(clientIP)
	token, err := util.RandomHex(16)
	if err != nil {
		return nil, err
	}

	hashedToken := hashToken(token)

	now := getTime().Unix()

	userToken := userAuthToken{
		UserId:        userId,
		AuthToken:     hashedToken,
		PrevAuthToken: hashedToken,
		ClientIp:      clientIP,
		UserAgent:     userAgent,
		RotatedAt:     now,
		CreatedAt:     now,
		UpdatedAt:     now,
		SeenAt:        0,
		AuthTokenSeen: false,
	}
	_, err = s.SQLStore.NewSession().Insert(&userToken)
	if err != nil {
		return nil, err
	}

	userToken.UnhashedToken = token

	return &userToken, nil
}

func (s *UserAuthTokenServiceImpl) LookupToken(unhashedToken string) (*userAuthToken, error) {
	hashedToken := hashToken(unhashedToken)
	if setting.Env == setting.DEV {
		s.log.Info("looking up token", "unhashed", unhashedToken, "hashed", hashedToken)
	}

	expireBefore := getTime().Add(time.Duration(-86400*s.Cfg.LoginCookieMaxDays) * time.Second).Unix()

	var userToken userAuthToken
	exists, err := s.SQLStore.NewSession().Where("(auth_token = ? OR prev_auth_token = ?) AND created_at > ?", hashedToken, hashedToken, expireBefore).Get(&userToken)
	if err != nil {
		return nil, err
	}

	if !exists {
		return nil, ErrAuthTokenNotFound
	}

	if userToken.AuthToken != hashedToken && userToken.PrevAuthToken == hashedToken && userToken.AuthTokenSeen {
		userTokenCopy := userToken
		userTokenCopy.AuthTokenSeen = false
		expireBefore := getTime().Add(-UrgentRotateTime).Unix()
		affectedRows, err := s.SQLStore.NewSession().Where("id = ? AND prev_auth_token = ? AND rotated_at < ?", userTokenCopy.Id, userTokenCopy.PrevAuthToken, expireBefore).AllCols().Update(&userTokenCopy)
		if err != nil {
			return nil, err
		}

		if affectedRows == 0 {
			s.log.Debug("prev seen token unchanged", "userTokenId", userToken.Id, "userId", userToken.UserId, "authToken", userToken.AuthToken, "clientIP", userToken.ClientIp, "userAgent", userToken.UserAgent)
		} else {
			s.log.Debug("prev seen token", "userTokenId", userToken.Id, "userId", userToken.UserId, "authToken", userToken.AuthToken, "clientIP", userToken.ClientIp, "userAgent", userToken.UserAgent)
		}
	}

	if !userToken.AuthTokenSeen && userToken.AuthToken == hashedToken {
		userTokenCopy := userToken
		userTokenCopy.AuthTokenSeen = true
		userTokenCopy.SeenAt = getTime().Unix()
		affectedRows, err := s.SQLStore.NewSession().Where("id = ? AND auth_token = ?", userTokenCopy.Id, userTokenCopy.AuthToken).AllCols().Update(&userTokenCopy)
		if err != nil {
			return nil, err
		}

		if affectedRows == 1 {
			userToken = userTokenCopy
		}

		if affectedRows == 0 {
			s.log.Debug("seen wrong token", "userTokenId", userToken.Id, "userId", userToken.UserId, "authToken", userToken.AuthToken, "clientIP", userToken.ClientIp, "userAgent", userToken.UserAgent)
		} else {
			s.log.Debug("seen token", "userTokenId", userToken.Id, "userId", userToken.UserId, "authToken", userToken.AuthToken, "clientIP", userToken.ClientIp, "userAgent", userToken.UserAgent)
		}
	}

	userToken.UnhashedToken = unhashedToken

	return &userToken, nil
}

func (s *UserAuthTokenServiceImpl) RefreshToken(token *userAuthToken, clientIP, userAgent string) (bool, error) {
	if token == nil {
		return false, nil
	}

	now := getTime()

	needsRotation := false
	rotatedAt := time.Unix(token.RotatedAt, 0)
	if token.AuthTokenSeen {
		needsRotation = rotatedAt.Before(now.Add(-time.Duration(s.Cfg.LoginCookieRotation) * time.Minute))
	} else {
		needsRotation = rotatedAt.Before(now.Add(-UrgentRotateTime))
	}

	if !needsRotation {
		return false, nil
	}

	s.log.Debug("refresh token needs rotation?", "auth_token_seen", token.AuthTokenSeen, "rotated_at", rotatedAt, "token.Id", token.Id)

	clientIP = util.ParseIPAddress(clientIP)
	newToken, _ := util.RandomHex(16)
	hashedToken := hashToken(newToken)

	// very important that auth_token_seen is set after the prev_auth_token = case when ... for mysql to function correctly
	sql := `
		UPDATE user_auth_token
		SET
			seen_at = 0,
			user_agent = ?,
			client_ip = ?,
			prev_auth_token = case when auth_token_seen = ? then auth_token else prev_auth_token end,
			auth_token = ?,
			auth_token_seen = ?,
			rotated_at = ?
		WHERE id = ? AND (auth_token_seen = ? OR rotated_at < ?)`

	res, err := s.SQLStore.NewSession().Exec(sql, userAgent, clientIP, s.SQLStore.Dialect.BooleanStr(true), hashedToken, s.SQLStore.Dialect.BooleanStr(false), now.Unix(), token.Id, s.SQLStore.Dialect.BooleanStr(true), now.Add(-30*time.Second).Unix())
	if err != nil {
		return false, err
	}

	affected, _ := res.RowsAffected()
	s.log.Debug("rotated", "affected", affected, "auth_token_id", token.Id, "userId", token.UserId)
	if affected > 0 {
		token.UnhashedToken = newToken
		return true, nil
	}

	return false, nil
}

func hashToken(token string) string {
	hashBytes := sha256.Sum256([]byte(token + setting.SecretKey))
	return hex.EncodeToString(hashBytes[:])
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"os"

	"github.com/jstemmer/go-junit-report/formatter"
	"github.com/jstemmer/go-junit-report/parser"
)

var (
	Version   = "v0.9.1-dev"
	Revision  = "HEAD"
	BuildTime string
)

var (
	noXMLHeader   = flag.Bool("no-xml-header", false, "do not print xml header")
	packageName   = flag.String("package-name", "", "specify a package name (compiled test have no package name in output)")
	goVersionFlag = flag.String("go-version", "", "specify the value to use for the go.version property in the generated XML")
	setExitCode   = flag.Bool("set-exit-code", false, "set exit code to 1 if tests failed")
	version       = flag.Bool("version", false, "print version")
)

func main() {
	flag.Parse()

	if *version {
		fmt.Printf("go-junit-report %s %s (%s)\n", Version, BuildTime, Revision)
		return
	}

	if flag.NArg() != 0 {
		fmt.Fprintf(os.Stderr, "%s does not accept positional arguments\n", os.Args[0])
		flag.Usage()
		os.Exit(1)
	}

	// Read input
	report, err := parser.Parse(os.Stdin, *packageName)
	if err != nil {
		fmt.Printf("Error reading input: %s\n", err)
		os.Exit(1)
	}

	// Write xml
	err = formatter.JUnitReportXML(report, *noXMLHeader, *goVersionFlag, os.Stdout)
	if err != nil {
		fmt.Printf("Error writing XML: %s\n", err)
		os.Exit(1)
	}

	if *setExitCode && report.Failures() > 0 {
		os.Exit(1)
	}
}
<commit_msg>Bump version to v1.0.0<commit_after>package main

import (
	"flag"
	"fmt"
	"os"

	"github.com/jstemmer/go-junit-report/formatter"
	"github.com/jstemmer/go-junit-report/parser"
)

var (
	Version   = "v1.0.0-dev"
	Revision  = "HEAD"
	BuildTime string
)

var (
	noXMLHeader   = flag.Bool("no-xml-header", false, "do not print xml header")
	packageName   = flag.String("package-name", "", "specify a package name (compiled test have no package name in output)")
	goVersionFlag = flag.String("go-version", "", "specify the value to use for the go.version property in the generated XML")
	setExitCode   = flag.Bool("set-exit-code", false, "set exit code to 1 if tests failed")
	version       = flag.Bool("version", false, "print version")
)

func main() {
	flag.Parse()

	if *version {
		fmt.Printf("go-junit-report %s %s (%s)\n", Version, BuildTime, Revision)
		return
	}

	if flag.NArg() != 0 {
		fmt.Fprintf(os.Stderr, "%s does not accept positional arguments\n", os.Args[0])
		flag.Usage()
		os.Exit(1)
	}

	// Read input
	report, err := parser.Parse(os.Stdin, *packageName)
	if err != nil {
		fmt.Printf("Error reading input: %s\n", err)
		os.Exit(1)
	}

	// Write xml
	err = formatter.JUnitReportXML(report, *noXMLHeader, *goVersionFlag, os.Stdout)
	if err != nil {
		fmt.Printf("Error writing XML: %s\n", err)
		os.Exit(1)
	}

	if *setExitCode && report.Failures() > 0 {
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package medtronic

import (
	"fmt"
	"log"
	"os"
	"strconv"
	"time"

	"github.com/ecc1/medtronic/cc1101"
	"github.com/ecc1/medtronic/radio"
	"github.com/ecc1/medtronic/rfm69"
)

const (
	freqEnvVar       = "MEDTRONIC_FREQUENCY"
	defaultFrequency = 916600000
	defaultTimeout   = 500 * time.Millisecond
	defaultRetries   = 3
)

type Pump struct {
	Radio radio.Interface

	// 22 for 522/722, 23 for 523/723, etc.
	family int

	// Implicit parameters for command execution.
	timeout time.Duration
	retries int
	rssi    int
	err     error

	DecodingErrors int
	CrcErrors      int
}

func Open() *Pump {
	pump := &Pump{
		timeout: defaultTimeout,
		retries: defaultRetries,
	}
	pump.Radio = cc1101.Open()
	if pump.Error() != nil {
		pump.SetError(nil)
		pump.Radio = rfm69.Open()
	}
	if pump.Error() != nil {
		pump.SetError(fmt.Errorf("no radio hardware detected"))
		return pump
	}
	log.Printf("connected to %s radio", pump.Radio.Hardware().Name())
	freq := getFrequency()
	log.Printf("setting frequency to %s", radio.MegaHertz(freq))
	pump.Radio.Init(freq)
	return pump
}

func getFrequency() uint32 {
	s := os.Getenv(freqEnvVar)
	if len(s) == 0 {
		return uint32(defaultFrequency)
	}
	f, err := strconv.ParseFloat(s, 64)
	if err != nil {
		log.Fatalf("%s: %v", freqEnvVar, err)
	}
	if 860.0 <= f && f <= 920.0 {
		return uint32(f * 1000000.0)
	}
	if 860000000.0 <= f && f <= 920000000.0 {
		return uint32(f)
	}
	log.Fatalf("%s (%s): invalid pump frequency", freqEnvVar, s)
	panic("unreachable")
}

func (pump *Pump) Timeout() time.Duration {
	return pump.timeout
}

func (pump *Pump) SetTimeout(t time.Duration) {
	pump.timeout = t
}

func (pump *Pump) Retries() int {
	return pump.retries
}

func (pump *Pump) SetRetries(n int) {
	pump.retries = n
}

func (pump *Pump) Rssi() int {
	return pump.rssi
}

func (pump *Pump) Error() error {
	err := pump.Radio.Error()
	if err != nil {
		return err
	}
	return pump.err
}

func (pump *Pump) SetError(err error) {
	pump.Radio.SetError(err)
	pump.err = err
}
<commit_msg>Make radio auto-detection more uniform<commit_after>package medtronic

import (
	"fmt"
	"log"
	"os"
	"strconv"
	"time"

	"github.com/ecc1/medtronic/cc1101"
	"github.com/ecc1/medtronic/radio"
	"github.com/ecc1/medtronic/rfm69"
)

const (
	freqEnvVar       = "MEDTRONIC_FREQUENCY"
	defaultFrequency = 916600000
	defaultTimeout   = 500 * time.Millisecond
	defaultRetries   = 3
)

type Pump struct {
	Radio radio.Interface

	// 22 for 522/722, 23 for 523/723, etc.
	family int

	// Implicit parameters for command execution.
	timeout time.Duration
	retries int
	rssi    int
	err     error

	DecodingErrors int
	CrcErrors      int
}

var radios = [](func() radio.Interface){cc1101.Open, rfm69.Open}

func Open() *Pump {
	pump := &Pump{
		timeout: defaultTimeout,
		retries: defaultRetries,
	}
	found := false
	for _, openRadio := range radios {
		pump.Radio = openRadio()
		if pump.Error() == nil {
			found = true
			break
		}
		_, wrongVersion := pump.Error().(radio.HardwareVersionError)
		if !wrongVersion {
			log.Print(pump.Error())
			break
		}
		pump.SetError(nil)
	}
	if !found {
		pump.SetError(fmt.Errorf("no radio hardware detected"))
		return pump
	}
	log.Printf("connected to %s radio", pump.Radio.Hardware().Name())
	freq := getFrequency()
	log.Printf("setting frequency to %s", radio.MegaHertz(freq))
	pump.Radio.Init(freq)
	return pump
}

func getFrequency() uint32 {
	s := os.Getenv(freqEnvVar)
	if len(s) == 0 {
		return uint32(defaultFrequency)
	}
	f, err := strconv.ParseFloat(s, 64)
	if err != nil {
		log.Fatalf("%s: %v", freqEnvVar, err)
	}
	if 860.0 <= f && f <= 920.0 {
		return uint32(f * 1000000.0)
	}
	if 860000000.0 <= f && f <= 920000000.0 {
		return uint32(f)
	}
	log.Fatalf("%s (%s): invalid pump frequency", freqEnvVar, s)
	panic("unreachable")
}

func (pump *Pump) Timeout() time.Duration {
	return pump.timeout
}

func (pump *Pump) SetTimeout(t time.Duration) {
	pump.timeout = t
}

func (pump *Pump) Retries() int {
	return pump.retries
}

func (pump *Pump) SetRetries(n int) {
	pump.retries = n
}

func (pump *Pump) Rssi() int {
	return pump.rssi
}

func (pump *Pump) Error() error {
	err := pump.Radio.Error()
	if err != nil {
		return err
	}
	return pump.err
}

func (pump *Pump) SetError(err error) {
	pump.Radio.SetError(err)
	pump.err = err
}
<|endoftext|>


<commit_before>package donut

import (
	"io"
	"os"
	"path"
	"sort"
	"strings"

	"encoding/json"
	"io/ioutil"
	"path/filepath"
)

type localDirectoryNode struct {
	root string
}

func (d localDirectoryNode) CreateBucket(bucket string) error {
	objectPath := path.Join(d.root, bucket)
	return os.MkdirAll(objectPath, 0700)
}

func (d localDirectoryNode) GetBuckets() ([]string, error) {
	files, err := ioutil.ReadDir(d.root)
	if err != nil {
		return nil, err
	}
	var results []string
	for _, file := range files {
		if file.IsDir() {
			results = append(results, file.Name())
		}
	}
	return results, nil
}

func (d localDirectoryNode) GetWriter(bucket, object string) (Writer, error) {
	objectPath := path.Join(d.root, bucket, object)
	err := os.MkdirAll(objectPath, 0700)
	if err != nil {
		return nil, err
	}
	return newDonutObjectWriter(objectPath)
}

func (d localDirectoryNode) GetReader(bucket, object string) (io.ReadCloser, error) {
	return os.Open(path.Join(d.root, bucket, object, "data"))
}

func (d localDirectoryNode) GetMetadata(bucket, object string) (map[string]string, error) {
	return d.getMetadata(bucket, object, "metadata.json")
}
func (d localDirectoryNode) GetDonutMetadata(bucket, object string) (map[string]string, error) {
	return d.getMetadata(bucket, object, "donutMetadata.json")
}

func (d localDirectoryNode) getMetadata(bucket, object, fileName string) (map[string]string, error) {
	file, err := os.Open(path.Join(d.root, bucket, object, fileName))
	defer file.Close()
	if err != nil {
		return nil, err
	}
	metadata := make(map[string]string)
	decoder := json.NewDecoder(file)
	if err := decoder.Decode(&metadata); err != nil {
		return nil, err
	}
	return metadata, nil

}

func (d localDirectoryNode) ListObjects(bucketName string) ([]string, error) {
	prefix := path.Join(d.root, bucketName)
	var objects []string
	if err := filepath.Walk(prefix, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if !info.IsDir() && strings.HasSuffix(path, "data") {
			object := strings.TrimPrefix(path, prefix+"/")
			object = strings.TrimSuffix(object, "/data")
			objects = append(objects, object)
		}
		return nil
	}); err != nil {
		return nil, err
	}
	sort.Strings(objects)
	return objects, nil
}
<commit_msg>Adding iodine to node_local<commit_after>package donut

import (
	"io"
	"os"
	"path"
	"sort"
	"strings"

	"encoding/json"
	"github.com/minio-io/iodine"
	"io/ioutil"
	"path/filepath"
)

type localDirectoryNode struct {
	root string
}

func (d localDirectoryNode) CreateBucket(bucket string) error {
	objectPath := path.Join(d.root, bucket)
	return iodine.Error(os.MkdirAll(objectPath, 0700), map[string]string{"bucket": bucket})
}

func (d localDirectoryNode) GetBuckets() ([]string, error) {
	files, err := ioutil.ReadDir(d.root)
	if err != nil {
		return nil, iodine.Error(err, nil)
	}
	var results []string
	for _, file := range files {
		if file.IsDir() {
			results = append(results, file.Name())
		}
	}
	return results, nil
}

func (d localDirectoryNode) GetWriter(bucket, object string) (Writer, error) {
	errParams := map[string]string{"bucket": bucket, "object": object}
	objectPath := path.Join(d.root, bucket, object)
	err := os.MkdirAll(objectPath, 0700)
	if err != nil {
		return nil, iodine.Error(err, errParams)
	}
	writer, err := newDonutObjectWriter(objectPath)
	return writer, iodine.Error(err, errParams)
}

func (d localDirectoryNode) GetReader(bucket, object string) (io.ReadCloser, error) {
	reader, err := os.Open(path.Join(d.root, bucket, object, "data"))
	return reader, iodine.Error(err, map[string]string{"bucket": bucket, "object": object})
}

func (d localDirectoryNode) GetMetadata(bucket, object string) (map[string]string, error) {
	m, err := d.getMetadata(bucket, object, "metadata.json")
	return m, iodine.Error(err, map[string]string{"bucket": bucket, "object": object})
}
func (d localDirectoryNode) GetDonutMetadata(bucket, object string) (map[string]string, error) {
	m, err := d.getMetadata(bucket, object, "donutMetadata.json")
	return m, iodine.Error(err, map[string]string{"bucket": bucket, "object": object})
}

func (d localDirectoryNode) getMetadata(bucket, object, fileName string) (map[string]string, error) {
	errParams := map[string]string{"bucket": bucket, "object": object, "file": fileName}
	file, err := os.Open(path.Join(d.root, bucket, object, fileName))
	defer file.Close()
	if err != nil {
		return nil, iodine.Error(err, errParams)
	}
	metadata := make(map[string]string)
	decoder := json.NewDecoder(file)
	if err := decoder.Decode(&metadata); err != nil {
		return nil, iodine.Error(err, errParams)
	}
	return metadata, nil

}

func (d localDirectoryNode) ListObjects(bucketName string) ([]string, error) {
	errParams := map[string]string{"bucket": bucketName}
	prefix := path.Join(d.root, bucketName)
	var objects []string
	if err := filepath.Walk(prefix, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return iodine.Error(err, errParams)
		}
		if !info.IsDir() && strings.HasSuffix(path, "data") {
			object := strings.TrimPrefix(path, prefix+"/")
			object = strings.TrimSuffix(object, "/data")
			objects = append(objects, object)
		}
		return nil
	}); err != nil {
		return nil, iodine.Error(err, errParams)
	}
	sort.Strings(objects)
	return objects, nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
)

type Octets uint32 // all 4 octets, in their respective bit locations

type OctsList []int // len always 4

func (o OctsList) Pack() Octets {
	return (Octets(o[0]) << 24) +
		(Octets(o[1]) << 16) +
		(Octets(o[2]) << 8) +
		(Octets(o[3]))
}

func (ino Octets) List() OctsList {
	return OctsList{
		int((0xFF000000 & ino) >> 24),
		int((0x00FF0000 & ino) >> 16),
		int((0x0000FF00 & ino) >> 8),
		int(0x000000FF & ino),
	}
}

type Addr struct {
	ip   OctsList
	mask OctsList
}

func (a *Addr) String() string {
	return fmt.Sprintf(
		"%d.%d.%d.%d/%d.%d.%d.%d",
		a.ip[0], a.ip[1], a.ip[2], a.ip[3],
		a.mask[0], a.mask[1], a.mask[2], a.mask[3])
}

func (a *Addr) Classful() (OctsList, string) {
	topOctet := a.ip[0]
	switch {
	case topOctet < 128:
		return OctsList{255, 0, 0, 0}, "A"
	case topOctet >= 128 && topOctet < 192:
		return OctsList{255, 255, 0, 0}, "B"
	case topOctet >= 192 && topOctet < 224:
		return OctsList{255, 255, 255, 0}, "C"
	default:
		panic("expected only classes A,B, or C")
	}
}

var hosts = []Addr{
	{ip: OctsList{9, 201, 195, 84}, mask: OctsList{255, 255, 240, 0}},
	{ip: OctsList{128, 10, 189, 215}, mask: OctsList{255, 255, 248, 0}},
	{ip: OctsList{135, 21, 243, 82}, mask: OctsList{255, 255, 224, 0}},
	{ip: OctsList{75, 149, 205, 61}, mask: OctsList{255, 255, 192, 0}},
	{ip: OctsList{7, 105, 198, 111}, mask: OctsList{255, 255, 252, 0}},
}

func main() {
	fmt.Printf("analyzing %d hosts ...\n", len(hosts))
	for _, addr := range hosts {
		ipPck := addr.ip.Pack()
		maskPck := addr.mask.Pack()
		hostMaskPck := ^addr.mask.Pack()

		classMask, klass := addr.Classful()
		classMaskPck := classMask.Pack()

		subnetMaskPck := (^classMaskPck) & maskPck
		fmt.Printf(
			"  network: %v (class %s masked)\n\t%v\n\tsubnet : %d <- %v\n\thost   : %d <- %v\n\n",
			(ipPck & classMaskPck).List(), klass,
			addr.String(),
			ipPck&subnetMaskPck, (ipPck & subnetMaskPck).List(),
			ipPck&hostMaskPck, (ipPck & hostMaskPck).List())
	}
}
<commit_msg>nit: accidentally exec bit from when was bash<commit_after>package main

import (
	"fmt"
)

type Octets uint32 // all 4 octets, in their respective bit locations

type OctsList []int // len always 4

func (o OctsList) Pack() Octets {
	return (Octets(o[0]) << 24) +
		(Octets(o[1]) << 16) +
		(Octets(o[2]) << 8) +
		(Octets(o[3]))
}

func (ino Octets) List() OctsList {
	return OctsList{
		int((0xFF000000 & ino) >> 24),
		int((0x00FF0000 & ino) >> 16),
		int((0x0000FF00 & ino) >> 8),
		int(0x000000FF & ino),
	}
}

type Addr struct {
	ip   OctsList
	mask OctsList
}

func (a *Addr) String() string {
	return fmt.Sprintf(
		"%d.%d.%d.%d/%d.%d.%d.%d",
		a.ip[0], a.ip[1], a.ip[2], a.ip[3],
		a.mask[0], a.mask[1], a.mask[2], a.mask[3])
}

func (a *Addr) Classful() (OctsList, string) {
	topOctet := a.ip[0]
	switch {
	case topOctet < 128:
		return OctsList{255, 0, 0, 0}, "A"
	case topOctet >= 128 && topOctet < 192:
		return OctsList{255, 255, 0, 0}, "B"
	case topOctet >= 192 && topOctet < 224:
		return OctsList{255, 255, 255, 0}, "C"
	default:
		panic("expected only classes A,B, or C")
	}
}

var hosts = []Addr{
	{ip: OctsList{9, 201, 195, 84}, mask: OctsList{255, 255, 240, 0}},
	{ip: OctsList{128, 10, 189, 215}, mask: OctsList{255, 255, 248, 0}},
	{ip: OctsList{135, 21, 243, 82}, mask: OctsList{255, 255, 224, 0}},
	{ip: OctsList{75, 149, 205, 61}, mask: OctsList{255, 255, 192, 0}},
	{ip: OctsList{7, 105, 198, 111}, mask: OctsList{255, 255, 252, 0}},
}

func main() {
	fmt.Printf("analyzing %d hosts ...\n", len(hosts))
	for _, addr := range hosts {
		ipPck := addr.ip.Pack()
		maskPck := addr.mask.Pack()
		hostMaskPck := ^addr.mask.Pack()

		classMask, klass := addr.Classful()
		classMaskPck := classMask.Pack()

		subnetMaskPck := (^classMaskPck) & maskPck
		fmt.Printf(
			"  network: %v (class %s masked)\n\t%v\n\tsubnet : %d <- %v\n\thost   : %d <- %v\n\n",
			(ipPck & classMaskPck).List(), klass,
			addr.String(),
			ipPck&subnetMaskPck, (ipPck & subnetMaskPck).List(),
			ipPck&hostMaskPck, (ipPck & hostMaskPck).List())
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"html"
	"log"
	"net/http"
	"strings"

	"github.com/go-telegram-bot-api/telegram-bot-api"
)

var bot *tgbotapi.BotAPI

func botRun() error {
	var err error
	bot, err = tgbotapi.NewBotAPI(cfg.Bot.Token)
	if err != nil {
		log.Fatal(err)
	}

	bot.Debug = cfg.Bot.Debug

	log.Println("Authorized on account:", bot.Self.UserName)

	_, err = bot.SetWebhook(tgbotapi.NewWebhookWithCert(fmt.Sprintf("%s%s/%s", cfg.HTTP.Host, cfg.HTTP.Port, cfg.Bot.Token), cfg.HTTP.PublicKey))
	if err != nil {
		log.Fatal(err)
	}

	updates := bot.ListenForWebhook(fmt.Sprintf("/%s", bot.Token))
	go func() {
		if err := http.ListenAndServeTLS(cfg.HTTP.Port, cfg.HTTP.PublicKey, cfg.HTTP.PrivateKey, nil); err != nil {
			log.Fatal(err)
		}
	}()

	for update := range updates {
		msgRouter(update)
	}
	return nil
}

func msgRouter(update tgbotapi.Update) error {
	switch {
	case update.InlineQuery != nil:
		return isInline(update)
	case update.Message != nil && update.Message.IsCommand():
		return isCommand(update)
	case update.Message != nil && (update.Message.Chat.IsPrivate() || bot.IsMessageToMe(*update.Message)):
		return isSearch(update)
	}
	return nil
}

func isCommand(update tgbotapi.Update) error {
	switch update.Message.Command() {
	case "s":
		return isSearch(update)
	case "daily":
		return isDaily(update)
	default:
		return sendMsg(update, HelpMsg)
	}
	return nil
}

func isSearch(update tgbotapi.Update) error {
	var msg string
	if update.Message.IsCommand() {
		msg = update.Message.CommandArguments()
	} else {
		msg = update.Message.Text
	}
	msg = strings.Trim(msg, " ")
	if msg == "" {
		return sendMsg(update, HelpMsg)
	}

	results, err := search(update.Message.Text, cfg.Zhihu.SearchResultNum)
	if err != nil {
		return err
	}

	msg = ""
	for _, result := range results {
		msg = fmt.Sprintf(`%s<a href="%s">%s</a>%s%s <a href="%s">...显示全部</a>%s%s`,
			msg, result.QuestionLink, result.Title, Warp, result.Summary, result.AnswerLink, Warp, Warp)
	}
	return sendMsg(update, msg)
}

func isInline(update tgbotapi.Update) error {
	msg := update.InlineQuery.Query
	results, err := search(msg, cfg.Zhihu.InlineResultNum)
	if err != nil {
		return err
	}
	var answers []interface{}
	for _, result := range results {
		content := html.EscapeString(result.Content)
		if len(content) > 2000 {
			content = Substr(content, 2000)
		}
		msg = fmt.Sprintf(`<a href="%s">%s</a>%s%s <a href="%s">...显示全部</a>%s%s`,
			result.QuestionLink, result.Title, Warp, content, result.AnswerLink, Warp, Warp)
		answer := tgbotapi.NewInlineQueryResultArticleHTML(result.ID, result.Title, msg)
		answer.Description = html.EscapeString(result.Summary)
		inputTextMessageContent := answer.InputMessageContent.(tgbotapi.InputTextMessageContent)
		inputTextMessageContent.DisableWebPagePreview = true
		answer.InputMessageContent = inputTextMessageContent
		answers = append(answers, &answer)
	}
	return answerInlineQuery(update, answers)
}

func isDaily(update tgbotapi.Update) error {
	txt, err := daily()
	if err != nil {
		return err
	}
	return sendMsg(update, txt)
}

func sendMsg(update tgbotapi.Update, txt string) error {
	msg := tgbotapi.NewMessage(update.Message.Chat.ID, txt)
	msg.ParseMode = "HTML"
	msg.DisableWebPagePreview = true
	if resp, err := bot.Send(msg); err != nil {
		log.Println("bot.Send:", err, resp)
		return err
	}
	return nil
}

func answerInlineQuery(update tgbotapi.Update, results []interface{}) error {
	answer := tgbotapi.InlineConfig{
		InlineQueryID: update.InlineQuery.ID,
		CacheTime:     3600,
		Results:       results,
	}
	if resp, err := bot.AnswerInlineQuery(answer); err != nil {
		log.Println("bot.answerInlineQuery:", err, resp)
		return err
	}
	return nil
}
<commit_msg>update<commit_after>package main

import (
	"fmt"
	"html"
	"log"
	"net/http"
	"strings"
)

var bot *tgbotapi.BotAPI

func botRun() error {
	var err error
	bot, err = tgbotapi.NewBotAPI(cfg.Bot.Token)
	if err != nil {
		log.Fatal(err)
	}

	bot.Debug = cfg.Bot.Debug

	log.Println("Authorized on account:", bot.Self.UserName)

	_, err = bot.SetWebhook(tgbotapi.NewWebhookWithCert(fmt.Sprintf("%s%s/%s", cfg.HTTP.Host, cfg.HTTP.Port, cfg.Bot.Token), cfg.HTTP.PublicKey))
	if err != nil {
		log.Fatal(err)
	}

	updates := bot.ListenForWebhook(fmt.Sprintf("/%s", bot.Token))
	go func() {
		if err := http.ListenAndServeTLS(cfg.HTTP.Port, cfg.HTTP.PublicKey, cfg.HTTP.PrivateKey, nil); err != nil {
			log.Fatal(err)
		}
	}()

	for update := range updates {
		msgRouter(update)
	}
	return nil
}

func msgRouter(update tgbotapi.Update) error {
	switch {
	case update.InlineQuery != nil:
		return isInline(update)
	case update.Message != nil && update.Message.IsCommand():
		return isCommand(update)
	case update.Message != nil && (update.Message.Chat.IsPrivate() || bot.IsMessageToMe(*update.Message)):
		return isSearch(update)
	default:
		log.Printf("unsupport message:%#v\n", update)
	}
	return nil
}

func isCommand(update tgbotapi.Update) error {
	switch update.Message.Command() {
	case "s":
		return isSearch(update)
	case "daily":
		return isDaily(update)
	default:
		return sendMsg(update, HelpMsg)
	}
	return nil
}

func isSearch(update tgbotapi.Update) error {
	var msg string
	if update.Message.IsCommand() {
		msg = update.Message.CommandArguments()
	} else {
		msg = update.Message.Text
	}
	msg = strings.Trim(msg, " ")
	if msg == "" {
		return sendMsg(update, HelpMsg)
	}

	results, err := search(update.Message.Text, cfg.Zhihu.SearchResultNum)
	if err != nil {
		return err
	}

	msg = ""
	for _, result := range results {
		msg = fmt.Sprintf(`%s<a href="%s">%s</a>%s%s <a href="%s">...显示全部</a>%s%s`,
			msg, result.QuestionLink, result.Title, Warp, result.Summary, result.AnswerLink, Warp, Warp)
	}
	return sendMsg(update, msg)
}

func isInline(update tgbotapi.Update) error {
	msg := update.InlineQuery.Query
	results, err := search(msg, cfg.Zhihu.InlineResultNum)
	if err != nil {
		return err
	}
	var answers []interface{}
	for _, result := range results {
		content := html.EscapeString(result.Content)
		if len(content) > 2000 {
			content = Substr(content, 2000)
		}
		msg = fmt.Sprintf(`<a href="%s">%s</a>%s%s <a href="%s">...显示全部</a>%s%s`,
			result.QuestionLink, result.Title, Warp, content, result.AnswerLink, Warp, Warp)
		answer := tgbotapi.NewInlineQueryResultArticleHTML(result.ID, result.Title, msg)
		answer.Description = html.EscapeString(result.Summary)
		inputTextMessageContent := answer.InputMessageContent.(tgbotapi.InputTextMessageContent)
		inputTextMessageContent.DisableWebPagePreview = true
		answer.InputMessageContent = inputTextMessageContent
		answers = append(answers, &answer)
	}
	return answerInlineQuery(update, answers)
}

func isDaily(update tgbotapi.Update) error {
	txt, err := daily()
	if err != nil {
		return err
	}
	return sendMsg(update, txt)
}

func sendMsg(update tgbotapi.Update, txt string) error {
	msg := tgbotapi.NewMessage(update.Message.Chat.ID, txt)
	msg.ParseMode = "HTML"
	msg.DisableWebPagePreview = true
	if resp, err := bot.Send(msg); err != nil {
		log.Println("bot.Send:", err, resp)
		return err
	}
	return nil
}

func answerInlineQuery(update tgbotapi.Update, results []interface{}) error {
	answer := tgbotapi.InlineConfig{
		InlineQueryID: update.InlineQuery.ID,
		CacheTime:     3600,
		Results:       results,
	}
	if resp, err := bot.AnswerInlineQuery(answer); err != nil {
		log.Println("bot.answerInlineQuery:", err, resp)
		return err
	}
	return nil
}
<|endoftext|>