<commit_before>package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"regexp"
	"strings"
)

// Macros we can handle and understand
var knownMacros = [...]string{".Nm", ".Op", ".Ar", ".Fl"}

func check(e error) {
	if e != nil {
		panic(e)
	}
}

type Command struct {
	name     string
	syntaxes []Syntax
}

type Syntax struct {
	parameters []Parameter
}

type Parameter struct {
	name         string
	optional     bool
	nospace      bool
	hasargument  bool
	argument     string
	hasflags     bool
	flags        string
	hasparameter bool
	parameter    *Parameter
}

func main() {
	files := getFileList("/usr/share/man/man1")

	//for _, file := range files[495:496] { // login
	for _, file := range files[450:510] {
		rawlines := loadFileToLines(file)
		lines := getSynopsisLines(rawlines)
		name := getDefinedName(rawlines)
		command, err := buildCommand(name, lines)
		fmt.Println(file)
		if err != nil {
			continue
		} else {
			fmt.Println(command)
		}
	}
}

func getFileList(path string) []string {
	filepaths := []string{}
	fileinfos, err := ioutil.ReadDir(path)

	if err != nil {
		fmt.Println("Failed to read directory %s", path)
	}

	for _, file := range fileinfos {
		if !(file.IsDir()) {
			filepaths = append(filepaths, path+"/"+file.Name())
		}
	}
	return filepaths
}

func loadFileToLines(path string) []string {
	data, err := ioutil.ReadFile(path)
	if err != nil {
		fmt.Println("Failed to read file at path: %s", path)
	}
	return strings.Split(string(data), "\n")
}

func quoteString(s string) string {
	return "\"" + s + "\""
}

func pass(s string) string {
	return s
}

// Determine if this is a synopsis heading. Some will be quoted/captialised
func isSynopsisLine(line string) bool {
	modfunctions := []func(string) string{quoteString, pass, strings.ToUpper}
	ret := false
	for _, shfunc := range modfunctions {
		for _, synfunc := range modfunctions {
			ret = ret || strings.HasPrefix(line, (shfunc(".Sh")+" "+synfunc("synopsis")))
		}
	}
	return ret
}

// Some man pages will define their name and use .Nm as shorthand
func getDefinedName(lines []string) string {
	re := regexp.MustCompile("^\\.Nm ([a-z]+)$")
	for _, line := range lines {
		result := re.FindAllStringSubmatch(line, -1)
		if len(result) > 0 {
			return strings.Split(result[0][0], " ")[1]
		}
	}
	return ""
}

func isNameLine(line string) bool {
	re := regexp.MustCompile("^\\.Nm( \\w+)?")
	return re.MatchString(line)
}

// Get all the lines below the synopsis heading
func getSynopsisLines(lines []string) [][]string {
	start := 0
	synopsis := [][]string{}
	usagePattern := -1

	for i, line := range lines {
		// Find the start of the synopsis section which contains the arguments
		if isSynopsisLine(line) {
			start = i
			continue
		}
		// Add lines until we reach the next section
		if start != 0 {
			if !(strings.HasPrefix(line, ".Sh") || strings.HasPrefix(line, ".SH")) {
				if compliantLine(line) {
					if isNameLine(line) {
						synopsis = append(synopsis, []string{})
						usagePattern++
					}
					synopsis[usagePattern] = append(synopsis[usagePattern], line)
				}
			} else {
				break
			}
		}
	}
	return synopsis
}

// Many synopsis sections are done using bold/italics rather than macros
// Let's ignore them because who knows what the author was thinking
func compliantLine(line string) bool {
	for _, macro := range knownMacros {
		if strings.Contains(line, macro) {
			return true
		}
	}
	return false
}

func buildCommand(name string, paramLines [][]string) (Command, error) {
	syntax := []Syntax{}
	var err error
	err = nil
	for _, lineset := range paramLines {
		syn, e := buildSyntax(lineset)
		if e != nil {
			err = e
		} else if isValidSyntax(syn) {
			syntax = append(syntax, syn)
		}
	}
	if len(syntax) == 0 {
		err = errors.New("No syntaxes found")
	}
	return Command{name: name, syntaxes: syntax}, err
}

func buildSyntax(lines []string) (Syntax, error) {
	parameters := []Parameter{}
	var err error
	err = nil
	for _, line := range lines {
		param, e := buildParameter(strings.Split(line, " "))
		if e != nil {
			err = e
		} else if isValidParameter(param) {
			parameters = append(parameters, param)
		}
	}
	return Syntax{parameters: parameters}, err
}

// Convert a string to an array of Parameters. The aggregate of these
// will form a Syntax and the set of Syntaxes forms a command. Most
// lines will only be a single parameter
func buildParameter(tokens []string) (Parameter, error) {
	p := Parameter{}
	var err error
	err = nil
	for i, rawtoken := range tokens {
		token := strings.TrimLeft(rawtoken, ".")
		if token == "Op" {
			if !p.optional {
				p.optional = true
			} else if !p.hasparameter {
				p.hasparameter = true
				tp, e := buildParameter(tokens[i:])
				if err != nil {
					err = e
				} else {
					p.parameter = &tp
				}
			}
		}

		if token == "Ar" && !p.hasargument {
			if !p.hasargument {
				p.hasargument = true
				// if the next token is blank, it's a generic non-named argument
				if len(tokens) > i+1 {
					p.argument = tokens[i+1]
				} else {
					p.argument = "files"
				}
			} else if !p.hasparameter {
				p.hasparameter = true
				tp, e := buildParameter(tokens[i:])
				if err != nil {
					err = e
				} else {
					p.parameter = &tp
				}
			}
		}

		if token == "Fl" && !p.hasflags {
			if !p.hasflags {
				p.hasflags = true
				p.flags = tokens[i+1]
			} else if !p.hasparameter {
				p.hasparameter = true
				tp, e := buildParameter(tokens[i:])
				if err != nil {
					err = e
				} else {
					p.parameter = &tp
				}
			}
		}
	}
	return p, err
}

func prependDashes(s string) string {
	lines := strings.Split(s, "\n")
	out := ""
	for _, line := range lines {
		if strings.TrimSpace(line) != "" {
			out = out + "--" + line + "\n"
		}
	}
	return out
}

func isValidParameter(p Parameter) bool {
	return (p.optional || p.nospace || p.hasflags || p.hasargument || p.hasparameter)
}

func isValidSyntax(s Syntax) bool {
	return (len(s.parameters) > 0)
}

func (c Command) String() string {
	ret := fmt.Sprintf("Command: %s\n", c.name)
	for _, syn := range c.syntaxes {
		ret = ret + prependDashes(syn.String()) + "\n"
	}
	return ret
}

func (s Syntax) String() string {
	ret := ""
	for _, param := range s.parameters {
		ret = ret + param.String() + "\n"
	}
	return ret
}

func (p Parameter) String() string {
	ret := ""
	if p.optional {
		ret = ret + "--optional\n"
	}
	if p.nospace {
		ret = ret + "--nospace\n"
	}
	if p.hasflags {
		ret = ret + fmt.Sprintf("--flags: %s\n", p.flags)
	}
	if p.hasargument {
		ret = ret + "--has argument: " + p.argument + "\n"
	}
	if p.hasparameter {
		ret = ret + "--has nested parameter:\n" + prependDashes(p.parameter.String())
	}
	if ret != "" {
		ret = "Parameter:\n" + ret
	}
	return ret
}
<commit_msg>Refactor main and support entire directory cleanly<commit_after>package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"regexp"
	"strings"
)

// Macros we can handle and understand
var knownMacros = [...]string{".Nm", ".Op", ".Ar", ".Fl"}

func check(e error) {
	if e != nil {
		panic(e)
	}
}

type Command struct {
	name     string
	syntaxes []Syntax
}

type Syntax struct {
	parameters []Parameter
}

type Parameter struct {
	name         string
	optional     bool
	nospace      bool
	hasargument  bool
	argument     string
	hasflags     bool
	flags        string
	hasparameter bool
	parameter    *Parameter
}

func main() {
	parseManFiles("/usr/share/man/man1", 0, 0)
}

func getFileList(path string) []string {
	filepaths := []string{}
	fileinfos, err := ioutil.ReadDir(path)

	if err != nil {
		fmt.Println("Failed to read directory %s", path)
	}

	for _, file := range fileinfos {
		if !(file.IsDir()) {
			filepaths = append(filepaths, path+"/"+file.Name())
		}
	}
	return filepaths
}

func parseManFiles(path string, rangeLower int, rangeUpper int) {
	files := getFileList(path)

	var s []string
	if rangeUpper == 0 && rangeLower == 0 {
		s = files[:]
	} else {
		s = files[rangeLower:rangeUpper]
	}

	//for _, file := range files[495:496] { // login debugging
	for _, file := range s {
		command, err := manfileToCommand(file)
		if err != nil {
			continue
		} else {
			fmt.Println(file)
			fmt.Println(command)
		}
	}
}

func manfileToCommand(path string) (Command, error) {
	rawlines := loadFileToLines(path)
	lines := getSynopsisLines(rawlines)
	name := getDefinedName(rawlines)
	command, err := buildCommand(name, lines)
	return command, err
}

func loadFileToLines(path string) []string {
	data, err := ioutil.ReadFile(path)
	if err != nil {
		fmt.Println("Failed to read file at path: %s", path)
	}
	return strings.Split(string(data), "\n")
}

func quoteString(s string) string {
	return "\"" + s + "\""
}

func pass(s string) string {
	return s
}

// Determine if this is a synopsis heading. Some will be quoted/captialised
func isSynopsisLine(line string) bool {
	modfunctions := []func(string) string{quoteString, pass, strings.ToUpper}
	ret := false
	for _, shfunc := range modfunctions {
		for _, synfunc := range modfunctions {
			ret = ret || strings.HasPrefix(line, (shfunc(".Sh")+" "+synfunc("synopsis")))
		}
	}
	return ret
}

// Some man pages will define their name and use .Nm as shorthand
func getDefinedName(lines []string) string {
	re := regexp.MustCompile("^\\.Nm ([a-z]+)$")
	for _, line := range lines {
		result := re.FindAllStringSubmatch(line, -1)
		if len(result) > 0 {
			return strings.Split(result[0][0], " ")[1]
		}
	}
	return ""
}

func isNameLine(line string) bool {
	re := regexp.MustCompile("^\\.Nm( \\w+)?")
	return re.MatchString(line)
}

// Get all the lines below the synopsis heading
func getSynopsisLines(lines []string) [][]string {
	start := 0
	synopsis := [][]string{}
	usagePattern := -1

	for i, line := range lines {
		// Find the start of the synopsis section which contains the arguments
		if isSynopsisLine(line) {
			start = i
			continue
		}
		// Add lines until we reach the next section
		if start != 0 {
			if !(strings.HasPrefix(line, ".Sh") || strings.HasPrefix(line, ".SH")) {
				if compliantLine(line) {
					// Usually a name line is at the start, but a couple don't do this.
					// The command is printed regardless, eg rlogin
					if isNameLine(line) || usagePattern == -1 {
						synopsis = append(synopsis, []string{})
						usagePattern++
					}
					synopsis[usagePattern] = append(synopsis[usagePattern], line)
				}
			} else {
				break
			}
		}
	}
	return synopsis
}

// Many synopsis sections are done using bold/italics rather than macros
// Let's ignore them because who knows what the author was thinking
func compliantLine(line string) bool {
	for _, macro := range knownMacros {
		if strings.Contains(line, macro) {
			return true
		}
	}
	return false
}

func buildCommand(name string, paramLines [][]string) (Command, error) {
	syntax := []Syntax{}
	var err error
	err = nil
	for _, lineset := range paramLines {
		syn, e := buildSyntax(lineset)
		if e != nil {
			err = e
		} else if isValidSyntax(syn) {
			syntax = append(syntax, syn)
		}
	}
	if len(syntax) == 0 {
		err = errors.New("No syntaxes found")
	}
	return Command{name: name, syntaxes: syntax}, err
}

func buildSyntax(lines []string) (Syntax, error) {
	parameters := []Parameter{}
	var err error
	err = nil
	for _, line := range lines {
		param, e := buildParameter(strings.Split(line, " "))
		if e != nil {
			err = e
		} else if isValidParameter(param) {
			parameters = append(parameters, param)
		}
	}
	return Syntax{parameters: parameters}, err
}

// Convert a string to an array of Parameters. The aggregate of these
// will form a Syntax and the set of Syntaxes forms a command. Most
// lines will only be a single parameter
func buildParameter(tokens []string) (Parameter, error) {
	p := Parameter{}
	var err error
	err = nil
	for i, rawtoken := range tokens {
		token := strings.TrimLeft(rawtoken, ".")
		if token == "Op" {
			if !p.optional {
				p.optional = true
			} else if !p.hasparameter {
				p.hasparameter = true
				tp, e := buildParameter(tokens[i:])
				if err != nil {
					err = e
				} else {
					p.parameter = &tp
				}
			}
		}

		if token == "Ar" && !p.hasargument {
			if !p.hasargument {
				p.hasargument = true
				// if the next token is blank, it's a generic non-named argument
				if len(tokens) > i+1 {
					p.argument = tokens[i+1]
				} else {
					p.argument = "files"
				}
			} else if !p.hasparameter {
				p.hasparameter = true
				tp, e := buildParameter(tokens[i:])
				if err != nil {
					err = e
				} else {
					p.parameter = &tp
				}
			}
		}

		if token == "Fl" && !p.hasflags {
			if !p.hasflags {
				p.hasflags = true
				if len(tokens) > i+1 {
					p.flags = tokens[i+1]
				} else {
					p.flags = "-"
				}
			} else if !p.hasparameter {
				p.hasparameter = true
				tp, e := buildParameter(tokens[i:])
				if err != nil {
					err = e
				} else {
					p.parameter = &tp
				}
			}
		}
	}
	return p, err
}

func prependDashes(s string) string {
	lines := strings.Split(s, "\n")
	out := ""
	for _, line := range lines {
		if strings.TrimSpace(line) != "" {
			out = out + "--" + line + "\n"
		}
	}
	return out
}

func isValidParameter(p Parameter) bool {
	return (p.optional || p.nospace || p.hasflags || p.hasargument || p.hasparameter)
}

func isValidSyntax(s Syntax) bool {
	return (len(s.parameters) > 0)
}

func (c Command) String() string {
	ret := fmt.Sprintf("Command: %s\n", c.name)
	for _, syn := range c.syntaxes {
		ret = ret + prependDashes(syn.String()) + "\n"
	}
	return ret
}

func (s Syntax) String() string {
	ret := ""
	for _, param := range s.parameters {
		ret = ret + param.String() + "\n"
	}
	return ret
}

func (p Parameter) String() string {
	ret := ""
	if p.optional {
		ret = ret + "--optional\n"
	}
	if p.nospace {
		ret = ret + "--nospace\n"
	}
	if p.hasflags {
		ret = ret + fmt.Sprintf("--flags: %s\n", p.flags)
	}
	if p.hasargument {
		ret = ret + "--has argument: " + p.argument + "\n"
	}
	if p.hasparameter {
		ret = ret + "--has nested parameter:\n" + prependDashes(p.parameter.String())
	}
	if ret != "" {
		ret = "Parameter:\n" + ret
	}
	return ret
}
<|endoftext|>


<commit_before>package echoperm

import (
	"net/http"

	"github.com/labstack/echo"
	"github.com/labstack/echo/engine/standard"
	"github.com/xyproto/permissions2"
	"github.com/xyproto/pinterface"
)

// Set up a middleware handler for Echo
func Middleware(redisHostname, redisPassword, denyMessage string) (echo.MiddlewareFunc, pinterface.IUserState, error) {
	userstate, err := permissions.NewUserStateWithPassword2(redisHostname, redisPassword)
	if err != nil {
		return nil, nil, err
	}
	perm := permissions.NewPermissions(userstate)

	return echo.MiddlewareFunc(func(next echo.HandlerFunc) echo.HandlerFunc {
		return echo.HandlerFunc(func(c echo.Context) error {
			// Check if the user has the right admin/user rights
			if perm.Rejected(c.Response().(*standard.Response).ResponseWriter, c.Request().(*standard.Request).Request) {
				// Deny the request
				return echo.NewHTTPError(http.StatusForbidden, denyMessage)
			}
			// Continue the chain of middleware
			return next(c)
		})
	}), userstate, nil
}
<commit_msg>Better comment<commit_after>package echoperm

import (
	"net/http"

	"github.com/labstack/echo"
	"github.com/labstack/echo/engine/standard"
	"github.com/xyproto/permissions2"
	"github.com/xyproto/pinterface"
)

// Middleware sets up a middleware handler for Echo.
//
// Requires a Redis hostname (can be "localhost"), a Redis password (can be
// blank) and a custom message for when permissions are denied.
func Middleware(redisHostname, redisPassword, denyMessage string) (echo.MiddlewareFunc, pinterface.IUserState, error) {
	userstate, err := permissions.NewUserStateWithPassword2(redisHostname, redisPassword)
	if err != nil {
		return nil, nil, err
	}
	perm := permissions.NewPermissions(userstate)

	return echo.MiddlewareFunc(func(next echo.HandlerFunc) echo.HandlerFunc {
		return echo.HandlerFunc(func(c echo.Context) error {
			// Check if the user has the right admin/user rights
			if perm.Rejected(c.Response().(*standard.Response).ResponseWriter, c.Request().(*standard.Request).Request) {
				// Deny the request
				return echo.NewHTTPError(http.StatusForbidden, denyMessage)
			}
			// Continue the chain of middleware
			return next(c)
		})
	}), userstate, nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/jinzhu/now"
	"github.com/parnurzeal/gorequest"
	"github.com/robfig/cron"
	"log"
	"os"
	"time"
)

const URL = "http://1337online.com/manager.php"
const TimeFormat = "2006-01-02 15:04:05.000000"
const TimeZone = "Europe/Amsterdam"

type requestBody struct {
	Action string `json:"action"`
	Data   string `json:"data"`
}

func main() {
	c := cron.New()
	c.AddFunc("0 37 13 * * *", func() {
		fmt.Println("Determine timeout")
		duration, err := determineTimeout(100, time.Millisecond*50)
		if err != nil {
			log.Fatal(err)
			return
		}
		startTime := now.MustParse("13:37:00").Add(-(*duration))
		<-time.After(startTime.Sub(time.Now()))
		gorequest.New().Post(URL).Type("form").Send(requestBody{
			Action: "new",
			Data:   os.Getenv("USERNAME"),
		}).End()
		fmt.Println("Posted!")
	})
	c.Start()
	fmt.Printf("1337 Bot cron started at %s\n", time.Now())

	// Never quit...
	select {}
}

func getServerTime() (*time.Time, error) {
	_, body, errors := gorequest.New().Post(URL).Type("form").Send(requestBody{
		Action: "getServerTime",
	}).End()

	if len(errors) > 0 {
		return nil, fmt.Errorf("Error in communication with server")
	}
	loc, _ := time.LoadLocation(TimeZone)

	serverTime, err := time.ParseInLocation(TimeFormat, body, loc)
	return &serverTime, err
}

func determineTimeout(samples int, interval time.Duration) (*time.Duration, error) {
	var minDuration *time.Duration
	for i := 0; i < samples; i++ {
		<-time.After(interval)
		start := time.Now()
		serverTime, err := getServerTime()
		if err != nil {
			return nil, err
		}
		duration := serverTime.Sub(start)
		if minDuration == nil || duration < *minDuration {
			minDuration = &duration
		}
	}

	return minDuration, nil
}
<commit_msg>Fixed time to start<commit_after>package main

import (
	"fmt"
	"github.com/jinzhu/now"
	"github.com/parnurzeal/gorequest"
	"github.com/robfig/cron"
	"log"
	"os"
	"time"
)

const URL = "http://1337online.com/manager.php"
const TimeFormat = "2006-01-02 15:04:05.000000"
const TimeZone = "Europe/Amsterdam"

type requestBody struct {
	Action string `json:"action"`
	Data   string `json:"data"`
}

func main() {
	c := cron.New()
	c.AddFunc("0 35 13 * * *", func() {
		fmt.Println("Determine timeout")
		duration, err := determineTimeout(200, time.Millisecond*50)
		if err != nil {
			log.Fatal(err)
			return
		}
		startTime := now.MustParse("13:37:00").Add(-(*duration))
		<-time.After(startTime.Sub(time.Now()))
		gorequest.New().Post(URL).Type("form").Send(requestBody{
			Action: "new",
			Data:   os.Getenv("USERNAME"),
		}).End()
		fmt.Println("Posted!")
	})
	c.Start()
	fmt.Printf("1337 Bot cron started at %s\n", time.Now())

	// Never quit...
	select {}
}

func getServerTime() (*time.Time, error) {
	_, body, errors := gorequest.New().Post(URL).Type("form").Send(requestBody{
		Action: "getServerTime",
	}).End()

	if len(errors) > 0 {
		return nil, fmt.Errorf("Error in communication with server")
	}
	loc, _ := time.LoadLocation(TimeZone)

	serverTime, err := time.ParseInLocation(TimeFormat, body, loc)
	return &serverTime, err
}

func determineTimeout(samples int, interval time.Duration) (*time.Duration, error) {
	var minDuration *time.Duration
	for i := 0; i < samples; i++ {
		<-time.After(interval)
		start := time.Now()
		serverTime, err := getServerTime()
		if err != nil {
			return nil, err
		}
		duration := serverTime.Sub(start)
		if minDuration == nil || duration < *minDuration {
			minDuration = &duration
		}
	}

	return minDuration, nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.

package tail

import (
	"bufio"
	"fmt"
	"github.com/ActiveState/tail/util"
	"github.com/ActiveState/tail/watch"
	"io"
	"io/ioutil"
	"launchpad.net/tomb"
	"log"
	"os"
	"time"
)

var (
	ErrStop = fmt.Errorf("tail should now stop")
)

type Line struct {
	Text string
	Time time.Time
	Err  error // Error from tail
}

// NewLine returns a Line with present time.
func NewLine(text string) *Line {
	return &Line{text, time.Now(), nil}
}

// SeekInfo represents arguments to `os.Seek`
type SeekInfo struct {
	Offset int64
	Whence int // os.SEEK_*
}

// Config is used to specify how a file must be tailed.
type Config struct {
	// File-specifc
	Location  *SeekInfo // Seek to this location before tailing
	ReOpen    bool      // Reopen recreated files (tail -F)
	MustExist bool      // Fail early if the file does not exist
	Poll      bool      // Poll for file changes instead of using inotify
	LimitRate int64     // Maximum read rate (lines per second)

	// Generic IO
	Follow      bool // Continue looking for new lines (tail -f)
	MaxLineSize int  // If non-zero, split longer lines into multiple lines

	// Logger, when nil, is set to tail.DefaultLogger
	// To disable logging: set field to tail.DiscardingLogger
	Logger *log.Logger
}

type Tail struct {
	Filename string
	Lines    chan *Line
	Config

	file    *os.File
	reader  *bufio.Reader
	watcher watch.FileWatcher
	changes *watch.FileChanges
	rateMon *RateMonitor

	tomb.Tomb // provides: Done, Kill, Dying
}

var (
	// DefaultLogger is used when Config.Logger == nil
	DefaultLogger = log.New(os.Stderr, "", log.LstdFlags)
	// DiscardingLogger can be used to disable logging output
	DiscardingLogger = log.New(ioutil.Discard, "", 0)
)

// TailFile begins tailing the file. Output stream is made available
// via the `Tail.Lines` channel. To handle errors during tailing,
// invoke the `Wait` or `Err` method after finishing reading from the
// `Lines` channel.
func TailFile(filename string, config Config) (*Tail, error) {
	if config.ReOpen && !config.Follow {
		util.Fatal("cannot set ReOpen without Follow.")
	}

	t := &Tail{
		Filename: filename,
		Lines:    make(chan *Line),
		Config:   config,
	}

	// when Logger was not specified in config, use default logger
	if t.Logger == nil {
		t.Logger = log.New(os.Stderr, "", log.LstdFlags)
	}

	t.rateMon = new(RateMonitor)

	if t.Poll {
		t.watcher = watch.NewPollingFileWatcher(filename)
	} else {
		t.watcher = watch.NewInotifyFileWatcher(filename)
	}

	if t.MustExist {
		var err error
		t.file, err = os.Open(t.Filename)
		if err != nil {
			return nil, err
		}
	}

	go t.tailFileSync()

	return t, nil
}

// Return the file's current position, like stdio's ftell().
// But this value is not very accurate.
// it may readed one line in the chan(tail.Lines),
// so it may lost one line.
func (tail *Tail) Tell() (offset int64, err error) {
	if tail.file == nil {
		return
	}
	offset, err = tail.file.Seek(0, os.SEEK_CUR)
	if err == nil {
		offset -= int64(tail.reader.Buffered())
	}
	return
}

// Stop stops the tailing activity.
func (tail *Tail) Stop() error {
	tail.Kill(nil)
	return tail.Wait()
}

func (tail *Tail) close() {
	close(tail.Lines)
	if tail.file != nil {
		tail.file.Close()
	}
}

func (tail *Tail) reopen() error {
	if tail.file != nil {
		tail.file.Close()
	}
	for {
		var err error
		tail.file, err = os.Open(tail.Filename)
		if err != nil {
			if os.IsNotExist(err) {
				tail.Logger.Printf("Waiting for %s to appear...", tail.Filename)
				if err := tail.watcher.BlockUntilExists(&tail.Tomb); err != nil {
					if err == tomb.ErrDying {
						return err
					}
					return fmt.Errorf("Failed to detect creation of %s: %s", tail.Filename, err)
				}
				continue
			}
			return fmt.Errorf("Unable to open file %s: %s", tail.Filename, err)
		}
		break
	}
	return nil
}

func (tail *Tail) readLine() ([]byte, error) {
	line, _, err := tail.reader.ReadLine()
	return line, err
}

func (tail *Tail) tailFileSync() {
	defer tail.Done()
	defer tail.close()

	if !tail.MustExist {
		// deferred first open.
		err := tail.reopen()
		if err != nil {
			if err != tomb.ErrDying {
				tail.Kill(err)
			}
			return
		}
	}

	// Seek to requested location on first open of the file.
	if tail.Location != nil {
		_, err := tail.file.Seek(tail.Location.Offset, tail.Location.Whence)
		tail.Logger.Printf("Seeked %s - %+v\n", tail.Filename, tail.Location)
		if err != nil {
			tail.Killf("Seek error on %s: %s", tail.Filename, err)
			return
		}
	}

	tail.reader = bufio.NewReader(tail.file)

	// Read line by line.
	for {
		line, err := tail.readLine()

		switch err {
		case nil:
			if line != nil {
				cooloff := !tail.sendLine(line)
				if cooloff {
					msg := "Too much activity; entering a cool-off period"
					tail.Lines <- &Line{
						msg,
						time.Now(),
						fmt.Errorf(msg)}
					// Wait a second before seeking till the end of
					// file when rate limit is reached.
					select {
					case <-time.After(time.Second):
					case <-tail.Dying():
						return
					}
					_, err := tail.file.Seek(0, 2) // Seek to fine end
					if err != nil {
						tail.Killf("Seek error on %s: %s", tail.Filename, err)
						return
					}
				}
			}
		case io.EOF:
			if !tail.Follow {
				return
			}
			// When EOF is reached, wait for more data to become
			// available. Wait strategy is based on the `tail.watcher`
			// implementation (inotify or polling).
			err := tail.waitForChanges()
			if err != nil {
				if err != ErrStop {
					tail.Kill(err)
				}
				return
			}
		default: // non-EOF error
			tail.Killf("Error reading %s: %s", tail.Filename, err)
			return
		}

		select {
		case <-tail.Dying():
			return
		default:
		}
	}
}

// waitForChanges waits until the file has been appended, deleted,
// moved or truncated. When moved or deleted - the file will be
// reopened if ReOpen is true. Truncated files are always reopened.
func (tail *Tail) waitForChanges() error {
	if tail.changes == nil {
		st, err := tail.file.Stat()
		if err != nil {
			return err
		}
		tail.changes = tail.watcher.ChangeEvents(&tail.Tomb, st)
	}

	select {
	case <-tail.changes.Modified:
		return nil
	case <-tail.changes.Deleted:
		tail.changes = nil
		if tail.ReOpen {
			// XXX: we must not log from a library.
			tail.Logger.Printf("Re-opening moved/deleted file %s ...", tail.Filename)
			if err := tail.reopen(); err != nil {
				return err
			}
			tail.Logger.Printf("Successfully reopened %s", tail.Filename)
			tail.reader = bufio.NewReader(tail.file)
			return nil
		} else {
			tail.Logger.Printf("Stopping tail as file no longer exists: %s", tail.Filename)
			return ErrStop
		}
	case <-tail.changes.Truncated:
		// Always reopen truncated files (Follow is true)
		tail.Logger.Printf("Re-opening truncated file %s ...", tail.Filename)
		if err := tail.reopen(); err != nil {
			return err
		}
		tail.Logger.Printf("Successfully reopened truncated %s", tail.Filename)
		tail.reader = bufio.NewReader(tail.file)
		return nil
	case <-tail.Dying():
		return ErrStop
	}
	panic("unreachable")
}

// sendLine sends the line(s) to Lines channel, splitting longer lines
// if necessary. Return false if rate limit is reached.
func (tail *Tail) sendLine(line []byte) bool {
	now := time.Now()
	nowUnix := now.Unix()
	lines := []string{string(line)}

	// Split longer lines
	if tail.MaxLineSize > 0 && len(line) > tail.MaxLineSize {
		lines = util.PartitionString(
			string(line), tail.MaxLineSize)
	}

	for _, line := range lines {
		tail.Lines <- &Line{line, now, nil}
		rate := tail.rateMon.Tick(nowUnix)
		if tail.LimitRate > 0 && rate > tail.LimitRate {
			tail.Logger.Printf("Rate limit (%v < %v) reached on file (%v); entering 1s cooloff period.\n",
				tail.LimitRate,
				rate,
				tail.Filename)
			return false
		}
	}

	return true
}

// Cleanup removes inotify watches added by the tail package. This function is
// meant to be invoked from a process's exit handler. Linux kernel may not
// automatically remove inotify watches after the process exits.
func Cleanup() {
	watch.Cleanup()
}
<commit_msg>Taking rat limiting out to remove sycalls to gettime (centos troubles).<commit_after>// Copyright (c) 2013 ActiveState Software Inc. All rights reserved.

package tail

import (
	"bufio"
	"fmt"
	"github.com/ActiveState/tail/util"
	"github.com/ActiveState/tail/watch"
	"io"
	"io/ioutil"
	"launchpad.net/tomb"
	"log"
	"os"
)

var (
	ErrStop = fmt.Errorf("tail should now stop")
)

type Line struct {
	Text string
	Err  error // Error from tail
}

// NewLine returns a Line with present time.
func NewLine(text string) *Line {
	return &Line{text, nil}
}

// SeekInfo represents arguments to `os.Seek`
type SeekInfo struct {
	Offset int64
	Whence int // os.SEEK_*
}

// Config is used to specify how a file must be tailed.
type Config struct {
	// File-specifc
	Location  *SeekInfo // Seek to this location before tailing
	ReOpen    bool      // Reopen recreated files (tail -F)
	MustExist bool      // Fail early if the file does not exist
	Poll      bool      // Poll for file changes instead of using inotify
	LimitRate int64     // Maximum read rate (lines per second)

	// Generic IO
	Follow      bool // Continue looking for new lines (tail -f)
	MaxLineSize int  // If non-zero, split longer lines into multiple lines

	// Logger, when nil, is set to tail.DefaultLogger
	// To disable logging: set field to tail.DiscardingLogger
	Logger *log.Logger
}

type Tail struct {
	Filename string
	Lines    chan *Line
	Config

	file    *os.File
	reader  *bufio.Reader
	watcher watch.FileWatcher
	changes *watch.FileChanges
	rateMon *RateMonitor

	tomb.Tomb // provides: Done, Kill, Dying
}

var (
	// DefaultLogger is used when Config.Logger == nil
	DefaultLogger = log.New(os.Stderr, "", log.LstdFlags)
	// DiscardingLogger can be used to disable logging output
	DiscardingLogger = log.New(ioutil.Discard, "", 0)
)

// TailFile begins tailing the file. Output stream is made available
// via the `Tail.Lines` channel. To handle errors during tailing,
// invoke the `Wait` or `Err` method after finishing reading from the
// `Lines` channel.
func TailFile(filename string, config Config) (*Tail, error) {
	if config.ReOpen && !config.Follow {
		util.Fatal("cannot set ReOpen without Follow.")
	}

	t := &Tail{
		Filename: filename,
		Lines:    make(chan *Line),
		Config:   config,
	}

	// when Logger was not specified in config, use default logger
	if t.Logger == nil {
		t.Logger = log.New(os.Stderr, "", log.LstdFlags)
	}

	t.rateMon = new(RateMonitor)

	if t.Poll {
		t.watcher = watch.NewPollingFileWatcher(filename)
	} else {
		t.watcher = watch.NewInotifyFileWatcher(filename)
	}

	if t.MustExist {
		var err error
		t.file, err = os.Open(t.Filename)
		if err != nil {
			return nil, err
		}
	}

	go t.tailFileSync()

	return t, nil
}

// Return the file's current position, like stdio's ftell().
// But this value is not very accurate.
// it may readed one line in the chan(tail.Lines),
// so it may lost one line.
func (tail *Tail) Tell() (offset int64, err error) {
	if tail.file == nil {
		return
	}
	offset, err = tail.file.Seek(0, os.SEEK_CUR)
	if err == nil {
		offset -= int64(tail.reader.Buffered())
	}
	return
}

// Stop stops the tailing activity.
func (tail *Tail) Stop() error {
	tail.Kill(nil)
	return tail.Wait()
}

func (tail *Tail) close() {
	close(tail.Lines)
	if tail.file != nil {
		tail.file.Close()
	}
}

func (tail *Tail) reopen() error {
	if tail.file != nil {
		tail.file.Close()
	}
	for {
		var err error
		tail.file, err = os.Open(tail.Filename)
		if err != nil {
			if os.IsNotExist(err) {
				tail.Logger.Printf("Waiting for %s to appear...", tail.Filename)
				if err := tail.watcher.BlockUntilExists(&tail.Tomb); err != nil {
					if err == tomb.ErrDying {
						return err
					}
					return fmt.Errorf("Failed to detect creation of %s: %s", tail.Filename, err)
				}
				continue
			}
			return fmt.Errorf("Unable to open file %s: %s", tail.Filename, err)
		}
		break
	}
	return nil
}

func (tail *Tail) readLine() ([]byte, error) {
	line, _, err := tail.reader.ReadLine()
	return line, err
}

func (tail *Tail) tailFileSync() {
	defer tail.Done()
	defer tail.close()

	if !tail.MustExist {
		// deferred first open.
		err := tail.reopen()
		if err != nil {
			if err != tomb.ErrDying {
				tail.Kill(err)
			}
			return
		}
	}

	// Seek to requested location on first open of the file.
	if tail.Location != nil {
		_, err := tail.file.Seek(tail.Location.Offset, tail.Location.Whence)
		tail.Logger.Printf("Seeked %s - %+v\n", tail.Filename, tail.Location)
		if err != nil {
			tail.Killf("Seek error on %s: %s", tail.Filename, err)
			return
		}
	}

	tail.reader = bufio.NewReader(tail.file)

	// Read line by line.
	for {
		line, err := tail.readLine()

		switch err {
		case nil:
			if line != nil {
				cooloff := !tail.sendLine(line)
				if cooloff {
					msg := "Too much activity; entering a cool-off period"
					tail.Lines <- &Line{
						msg,
						fmt.Errorf(msg)}
					_, err := tail.file.Seek(0, 2) // Seek to fine end
					if err != nil {
						tail.Killf("Seek error on %s: %s", tail.Filename, err)
						return
					}
				}
			}
		case io.EOF:
			if !tail.Follow {
				return
			}
			// When EOF is reached, wait for more data to become
			// available. Wait strategy is based on the `tail.watcher`
			// implementation (inotify or polling).
			err := tail.waitForChanges()
			if err != nil {
				if err != ErrStop {
					tail.Kill(err)
				}
				return
			}
		default: // non-EOF error
			tail.Killf("Error reading %s: %s", tail.Filename, err)
			return
		}

		select {
		case <-tail.Dying():
			return
		default:
		}
	}
}

// waitForChanges waits until the file has been appended, deleted,
// moved or truncated. When moved or deleted - the file will be
// reopened if ReOpen is true. Truncated files are always reopened.
func (tail *Tail) waitForChanges() error {
	if tail.changes == nil {
		st, err := tail.file.Stat()
		if err != nil {
			return err
		}
		tail.changes = tail.watcher.ChangeEvents(&tail.Tomb, st)
	}

	select {
	case <-tail.changes.Modified:
		return nil
	case <-tail.changes.Deleted:
		tail.changes = nil
		if tail.ReOpen {
			// XXX: we must not log from a library.
			tail.Logger.Printf("Re-opening moved/deleted file %s ...", tail.Filename)
			if err := tail.reopen(); err != nil {
				return err
			}
			tail.Logger.Printf("Successfully reopened %s", tail.Filename)
			tail.reader = bufio.NewReader(tail.file)
			return nil
		} else {
			tail.Logger.Printf("Stopping tail as file no longer exists: %s", tail.Filename)
			return ErrStop
		}
	case <-tail.changes.Truncated:
		// Always reopen truncated files (Follow is true)
		tail.Logger.Printf("Re-opening truncated file %s ...", tail.Filename)
		if err := tail.reopen(); err != nil {
			return err
		}
		tail.Logger.Printf("Successfully reopened truncated %s", tail.Filename)
		tail.reader = bufio.NewReader(tail.file)
		return nil
	case <-tail.Dying():
		return ErrStop
	}
	panic("unreachable")
}

// sendLine sends the line(s) to Lines channel, splitting longer lines
// if necessary. Return false if rate limit is reached.
func (tail *Tail) sendLine(line []byte) bool {
	lines := []string{string(line)}

	// Split longer lines
	if tail.MaxLineSize > 0 && len(line) > tail.MaxLineSize {
		lines = util.PartitionString(
			string(line), tail.MaxLineSize)
	}

	for _, line := range lines {
		tail.Lines <- &Line{line, nil}
	}

	return true
}

// Cleanup removes inotify watches added by the tail package. This function is
// meant to be invoked from a process's exit handler. Linux kernel may not
// automatically remove inotify watches after the process exits.
func Cleanup() {
	watch.Cleanup()
}
<|endoftext|>


<commit_before><commit_msg>Throttling to 200 simultaneous requests...<commit_after><|endoftext|>


<commit_before>// +build ignore

/*
 * Minio Client (C) 2014, 2015 Minio, Inc.
 *
 * 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 main

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"text/template"
	"time"

	"github.com/minio/cli"
	"github.com/minio/mc/pkg/console"
)

type Version struct {
	Date string
}

func writeVersion(version Version) error {
	var versionTemplate = `// --------  DO NOT EDIT --------
// this is an autogenerated file

package main

import (
	"net/http"
	"time"
)

// Version autogenerated
var Version = {{if .Date}}"{{.Date}}"{{else}}""{{end}}

// getVersion -
func getVersion() string {
	t, _ := time.Parse(time.RFC3339Nano, Version)
	if t.IsZero() {
		return ""
	}
	return t.Format(http.TimeFormat)
}
`
	t := template.Must(template.New("version").Parse(versionTemplate))
	versionFile, err := os.OpenFile("version.go", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
	if err != nil {
		return err
	}
	defer versionFile.Close()
	err = t.Execute(versionFile, version)
	if err != nil {
		return err
	}
	return nil
}

func runGoBuild(ctx *cli.Context) {
	if ctx.Args().First() == "help" {
		cli.ShowCommandHelpAndExit(ctx, "build", 1) // last argument is exit code
	}
	mcBuild := exec.Command("godep", "go", "build", "-a", "./...")
	mcTest := exec.Command("godep", "go", "test", "-race", "./...")
	mcInstall := exec.Command("godep", "go", "install", "-a", "github.com/minio/mc")
	mcBuildErr := mcBuild.Run()
	if mcBuildErr != nil {
		console.Fatalln(mcBuildErr)
	}
	var mcTestStdOut bytes.Buffer
	mcTest.Stdout = &mcTestStdOut
	mcTestErr := mcTest.Run()
	if mcTestErr != nil {
		fmt.Print(mcTestStdOut.String())
		console.Fatalln(mcTestErr)
	}
	fmt.Print(mcTestStdOut.String())
	mcInstallErr := mcInstall.Run()
	if mcInstallErr != nil {
		console.Fatalln(mcInstallErr)
	}
}

func runReleaseCmd(ctx *cli.Context) {
	if ctx.Args().First() == "help" {
		cli.ShowCommandHelpAndExit(ctx, "release", 1) // last argument is exit code
	}
	version := Version{Date: time.Now().UTC().Format(time.RFC3339Nano)}
	err := writeVersion(version)
	if err != nil {
		console.Fatalln(err)
	}
}

func main() {
	app := cli.NewApp()
	app.Usage = "Minio Client for object storage and filesystems"
	app.Commands = []cli.Command{
		{
			Name:   "release",
			Action: runReleaseCmd,
		}, {
			Name:   "install",
			Action: runGoBuild,
		},
	}
	app.Author = "Minio.io"
	app.RunAndExitOnError()
}
<commit_msg>Add proper error output in make.go<commit_after>// +build ignore

/*
 * Minio Client (C) 2014, 2015 Minio, Inc.
 *
 * 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 main

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"text/template"
	"time"

	"github.com/minio/cli"
)

type Version struct {
	Date string
}

func writeVersion(version Version) error {
	var versionTemplate = `// --------  DO NOT EDIT --------
// this is an autogenerated file

package main

import (
	"net/http"
	"time"
)

// Version autogenerated
var Version = {{if .Date}}"{{.Date}}"{{else}}""{{end}}

// getVersion -
func getVersion() string {
	t, _ := time.Parse(time.RFC3339Nano, Version)
	if t.IsZero() {
		return ""
	}
	return t.Format(http.TimeFormat)
}
`
	t := template.Must(template.New("version").Parse(versionTemplate))
	versionFile, err := os.OpenFile("version.go", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
	if err != nil {
		return err
	}
	defer versionFile.Close()
	err = t.Execute(versionFile, version)
	if err != nil {
		return err
	}
	return nil
}

type command struct {
	cmd    *exec.Cmd
	stderr *bytes.Buffer
	stdout *bytes.Buffer
}

func (c command) runCommand() error {
	c.cmd.Stdout = c.stdout
	c.cmd.Stderr = c.stderr
	return c.cmd.Run()
}
func (c command) String() string {
	message := c.stderr.String()
	message += c.stdout.String()
	return message
}

func runMcInstall(ctx *cli.Context) {
	if ctx.Args().First() == "help" {
		cli.ShowCommandHelpAndExit(ctx, "install", 1) // last argument is exit code
	}
	mcBuild := command{exec.Command("godep", "go", "build", "-a", "./..."), &bytes.Buffer{}, &bytes.Buffer{}}
	mcTest := command{exec.Command("godep", "go", "test", "-race", "./..."), &bytes.Buffer{}, &bytes.Buffer{}}
	mcInstall := command{exec.Command("godep", "go", "install", "-a", "github.com/minio/mc"), &bytes.Buffer{}, &bytes.Buffer{}}
	mcBuildErr := mcBuild.runCommand()
	if mcBuildErr != nil {
		fmt.Print(mcBuild)
		os.Exit(1)
	}
	fmt.Print(mcBuild)
	mcTestErr := mcTest.runCommand()
	if mcTestErr != nil {
		fmt.Println(mcTest)
		os.Exit(1)
	}
	fmt.Print(mcTest)
	mcInstallErr := mcInstall.runCommand()
	if mcInstallErr != nil {
		fmt.Println(mcInstall)
		os.Exit(1)
	}
	fmt.Print(mcInstall)
}

func runMcRelease(ctx *cli.Context) {
	if ctx.Args().First() == "help" {
		cli.ShowCommandHelpAndExit(ctx, "release", 1) // last argument is exit code
	}
	version := Version{Date: time.Now().UTC().Format(time.RFC3339Nano)}
	err := writeVersion(version)
	if err != nil {
		fmt.Print(err)
		os.Exit(1)
	}
}

func main() {
	app := cli.NewApp()
	app.Usage = "Minio Client for object storage and filesystems"
	app.Commands = []cli.Command{
		{
			Name:   "release",
			Action: runMcRelease,
		}, {
			Name:   "install",
			Action: runMcInstall,
		},
	}
	app.Author = "Minio.io"
	app.RunAndExitOnError()
}
<|endoftext|>


<commit_before>package dnssd

import (
	"context"
	"fmt"
	"net"

	"github.com/brutella/dnssd/log"
	"github.com/miekg/dns"
	"golang.org/x/net/ipv4"
	"golang.org/x/net/ipv6"
)

var (
	IPv4LinkLocalMulticast = net.ParseIP("224.0.0.251")
	IPv6LinkLocalMulticast = net.ParseIP("ff02::fb")

	AddrIPv4LinkLocalMulticast = &net.UDPAddr{
		IP:   IPv4LinkLocalMulticast,
		Port: 5353,
	}

	AddrIPv6LinkLocalMulticast = &net.UDPAddr{
		IP:   IPv6LinkLocalMulticast,
		Port: 5353,
	}

	TTLDefault  uint32 = 75 * 60 // Default ttl for mDNS resource records
	TTLHostname uint32 = 120     // TTL for mDNS resource records containing the host name
)

// Query is a mDNS query
type Query struct {
	msg   *dns.Msg       // The query message
	iface *net.Interface // The network interface to which the message is sent
}

// Response is a mDNS response
type Response struct {
	msg   *dns.Msg       // The response message
	addr  *net.UDPAddr   // Is nil for multicast response
	iface *net.Interface // The network interface to which the message is sent
}

// Request represents an incoming mDNS message
type Request struct {
	msg   *dns.Msg       // The message
	from  *net.UDPAddr   // The source addr of the message
	iface *net.Interface // The network interface from which the message was received
}

func (r Request) String() string {
	return fmt.Sprintf("%s@%s\n%v", r.from.IP, r.iface.Name, r.msg)
}

// MDNSConn represents a mDNS connection. It encapsulates an IPv4 and IPv6 UDP connection.
type MDNSConn interface {
	// SendQuery sends a mDNS query.
	SendQuery(q *Query) error

	// SendResponse sends a mDNS response
	SendResponse(resp *Response) error

	// Read returns a channel which receives mDNS messages
	Read(ctx context.Context) <-chan *Request

	// Clears the connection buffer
	Drain(ctx context.Context)

	// Close closes the connection
	Close()
}

type mdnsConn struct {
	ipv4 *ipv4.PacketConn
	ipv6 *ipv6.PacketConn
	ch   chan *Request
}

func NewMDNSConn() (MDNSConn, error) {
	return newMDNSConn()
}

func (c *mdnsConn) SendQuery(q *Query) error {
	return c.sendQuery(q.msg, q.iface)
}

func (c *mdnsConn) SendResponse(resp *Response) error {
	if resp.addr != nil {
		return c.sendResponseTo(resp.msg, resp.iface, resp.addr)
	}

	return c.sendResponse(resp.msg, resp.iface)
}

func (c *mdnsConn) Read(ctx context.Context) <-chan *Request {
	return c.read(ctx)
}

func (c *mdnsConn) Drain(ctx context.Context) {
	log.Debug.Println("Draining connection")
	for {
		select {
		case req := <-c.Read(ctx):
			log.Debug.Println("Ignoring msg from", req.from.IP)
			break
		default:
			return
		}
	}
}

func (c *mdnsConn) Close() {
	c.close()
}

func newMDNSConn() (*mdnsConn, error) {
	var errs []error
	var connIPv4 *ipv4.PacketConn
	var connIPv6 *ipv6.PacketConn

	if conn, err := net.ListenUDP("udp4", AddrIPv4LinkLocalMulticast); err != nil {
		errs = append(errs, err)
	} else {
		connIPv4 = ipv4.NewPacketConn(conn)
		connIPv4.SetControlMessage(ipv4.FlagInterface, true)

		for _, iface := range multicastInterfaces() {
			if err := connIPv4.JoinGroup(&iface, &net.UDPAddr{IP: IPv4LinkLocalMulticast}); err != nil {
				log.Debug.Printf("Failed joining IPv4 %v: %v", iface.Name, err)
			} else {
				log.Debug.Printf("Joined IPv4 %v", iface.Name)
			}
		}
	}

	if conn, err := net.ListenUDP("udp6", AddrIPv6LinkLocalMulticast); err != nil {
		errs = append(errs, err)
	} else {
		connIPv6 = ipv6.NewPacketConn(conn)
		connIPv6.SetControlMessage(ipv6.FlagInterface, true)

		for _, iface := range multicastInterfaces() {
			if err := connIPv6.JoinGroup(&iface, &net.UDPAddr{IP: IPv6LinkLocalMulticast}); err != nil {
				log.Debug.Printf("Failed joining IPv6 %v: %v", iface.Name, err)
			} else {
				log.Debug.Printf("Joined IPv6 %v", iface.Name)
			}
		}
	}

	if err := first(errs...); connIPv4 == nil && connIPv6 == nil {
		return nil, fmt.Errorf("Failed setting up UDP server: %v", err)
	}

	return &mdnsConn{
		ipv4: connIPv4,
		ipv6: connIPv6,
		ch:   make(chan *Request),
	}, nil
}

func (c *mdnsConn) enableMulticastLoopback(enable bool) {
	if c.ipv4 != nil {
		c.ipv4.SetMulticastLoopback(enable)
	}

	if c.ipv6 != nil {
		c.ipv6.SetMulticastLoopback(enable)
	}
}

func (c *mdnsConn) close() {
	if c.ipv4 != nil {
		c.ipv4.Close()
	}

	if c.ipv6 != nil {
		c.ipv6.Close()
	}
}

func (c *mdnsConn) read(ctx context.Context) <-chan *Request {
	c.readInto(ctx, c.ch)
	return c.ch
}

func (c *mdnsConn) readInto(ctx context.Context, ch chan *Request) {

	isDone := func(ctx context.Context) bool {
		return ctx.Err() == context.Canceled
	}

	if c.ipv4 != nil {
		go func() {
			buf := make([]byte, 65536)
			for {
				if isDone(ctx) {
					return
				}

				n, cm, from, err := c.ipv4.ReadFrom(buf)
				if err != nil {
					continue
				}

				udpAddr, ok := from.(*net.UDPAddr)
				if !ok {
					log.Info.Println("invalid source address")
					continue
				}

				var iface *net.Interface
				if cm != nil {
					iface, err = net.InterfaceByIndex(cm.IfIndex)
					if err != nil {
						continue
					}
				}

				if n > 0 {
					m := new(dns.Msg)
					if err := m.Unpack(buf); err == nil && !shouldIgnore(m) {
						ch <- &Request{m, udpAddr, iface}
					}
				}
			}
		}()
	}

	if c.ipv6 != nil {
		go func() {
			buf := make([]byte, 65536)
			for {
				if isDone(ctx) {
					return
				}

				n, cm, from, err := c.ipv6.ReadFrom(buf)
				if err != nil {
					continue
				}

				udpAddr, ok := from.(*net.UDPAddr)
				if !ok {
					log.Info.Println("invalid source address")
					continue
				}

				var iface *net.Interface
				if cm != nil {
					iface, err = net.InterfaceByIndex(cm.IfIndex)
					if err != nil {
						continue
					}
				}

				if n > 0 {
					m := new(dns.Msg)
					if err := m.Unpack(buf); err == nil && !shouldIgnore(m) {
						ch <- &Request{m, udpAddr, iface}
					}
				}
			}
		}()
	}
}

func (c *mdnsConn) sendQuery(m *dns.Msg, iface *net.Interface) error {
	sanitizeQuery(m)

	return c.writeMsg(m, iface)
}

func (c *mdnsConn) sendResponse(m *dns.Msg, iface *net.Interface) error {
	sanitizeResponse(m)

	return c.writeMsg(m, iface)
}

func (c *mdnsConn) sendResponseTo(m *dns.Msg, iface *net.Interface, addr *net.UDPAddr) error {
	sanitizeResponse(m)

	return c.writeMsgTo(m, iface, addr)
}

func (c *mdnsConn) writeMsg(m *dns.Msg, iface *net.Interface) error {
	var err error
	if c.ipv4 != nil {
		err = c.writeMsgTo(m, iface, AddrIPv4LinkLocalMulticast)
	}

	if c.ipv6 != nil {
		err = c.writeMsgTo(m, iface, AddrIPv6LinkLocalMulticast)
	}

	return err
}

func (c *mdnsConn) writeMsgTo(m *dns.Msg, iface *net.Interface, addr *net.UDPAddr) error {
	sanitizeMsg(m)

	var err error
	if c.ipv4 != nil && addr.IP.To4() != nil {
		if out, err := m.Pack(); err == nil {
			ctrl := &ipv4.ControlMessage{}
			if iface != nil {
				ctrl.IfIndex = iface.Index
			}
			_, err = c.ipv4.WriteTo(out, ctrl, addr)
		}
	}

	if c.ipv6 != nil && addr.IP.To4() == nil {
		if out, err := m.Pack(); err == nil {
			ctrl := &ipv6.ControlMessage{}
			if iface != nil {
				ctrl.IfIndex = iface.Index
			}
			_, err = c.ipv6.WriteTo(out, ctrl, addr)
		}
	}

	return err
}

func shouldIgnore(m *dns.Msg) bool {
	if m.Opcode != 0 {
		return true
	}

	if m.Rcode != 0 {
		return true
	}

	return false
}

func sanitizeResponse(m *dns.Msg) {
	if m.Question != nil && len(m.Question) > 0 {
		log.Info.Println("Multicast DNS responses MUST NOT contain any questions in the Question Section.  (RFC6762 6)")
		m.Question = nil
	}

	if !m.Response {
		log.Info.Println("In response messages the QR bit MUST be one (RFC6762 18.2)")
		m.Response = true
	}

	if !m.Authoritative {
		log.Info.Println("AA Bit bit MUST be set to one in response messages (RFC6762 18.4)")
		m.Authoritative = true
	}

	if m.Truncated {
		log.Info.Println("In multicast response messages, the TC bit MUST be zero on transmission. (RFC6762 18.5)")
		m.Truncated = false
	}
}

func sanitizeQuery(m *dns.Msg) {
	if m.Response {
		log.Info.Println("In query messages the QR bit MUST be zero (RFC6762 18.2)")
		m.Response = false
	}

	if m.Authoritative {
		log.Info.Println("AA Bit MUST be zero in query messages (RFC6762 18.4)")
		m.Authoritative = false
	}
}

func sanitizeMsg(m *dns.Msg) {
	if m.Opcode != 0 {
		log.Info.Println("In both multicast query and multicast response messages, the OPCODE MUST be zero on transmission (RFC6762 18.3)")
		m.Opcode = 0
	}

	if m.RecursionDesired {
		log.Info.Println("In both multicast query and multicast response messages, the Recursion Available bit MUST be zero on transmission. (RFC6762 18.7)")
		m.RecursionDesired = false
	}

	if m.Zero {
		log.Info.Println("In both query and response messages, the Zero bit MUST be zero on transmission (RFC6762 18.8)")
		m.Zero = false
	}

	if m.AuthenticatedData {
		log.Info.Println("In both multicast query and multicast response messages, the Authentic Data bit MUST be zero on transmission (RFC6762 18.9)")
		m.AuthenticatedData = false
	}

	if m.CheckingDisabled {
		log.Info.Println("In both multicast query and multicast response messages, the Checking Disabled bit MUST be zero on transmission (RFC6762 18.10)")
		m.CheckingDisabled = false
	}

	if m.Rcode != 0 {
		log.Info.Println("In both multicast query and multicast response messages, the Response Code MUST be zero on transmission. (RFC6762 18.11)")
		m.Rcode = 0
	}
}

func first(errs ...error) error {
	for _, err := range errs {
		if err != nil {
			return err
		}
	}

	return nil
}

// Sets the Top Bit of rrclass for all answer records (except PTR) to trigger a cache flush in the receivers.
func setAnswerCacheFlushBit(msg *dns.Msg) {
	// From RFC6762
	//    The most significant bit of the rrclass for a record in the Answer
	//    Section of a response message is the Multicast DNS cache-flush bit
	//    and is discussed in more detail below in Section 10.2, "Announcements
	//    to Flush Outdated Cache Entries".
	for _, a := range msg.Answer {
		switch a.(type) {
		case *dns.PTR:
			continue
		default:
			a.Header().Class |= (1 << 15)
		}
	}
}

// Sets the Top Bit of class to indicate the unicast responses are preferred for this question.
func setQuestionUnicast(q *dns.Question) {
	q.Qclass |= (1 << 15)
}

// Returns true if q requires unicast responses.
func isUnicastQuestion(q dns.Question) bool {
	// From RFC6762
	// 18.12.  Repurposing of Top Bit of qclass in Question Section
	//
	//    In the Question Section of a Multicast DNS query, the top bit of the
	//    qclass field is used to indicate that unicast responses are preferred
	//    for this particular question.  (See Section 5.4.)
	return q.Qclass&(1<<15) != 0
}
<commit_msg>Disable multicast loopback again<commit_after>package dnssd

import (
	"context"
	"fmt"
	"net"

	"github.com/brutella/dnssd/log"
	"github.com/miekg/dns"
	"golang.org/x/net/ipv4"
	"golang.org/x/net/ipv6"
)

var (
	IPv4LinkLocalMulticast = net.ParseIP("224.0.0.251")
	IPv6LinkLocalMulticast = net.ParseIP("ff02::fb")

	AddrIPv4LinkLocalMulticast = &net.UDPAddr{
		IP:   IPv4LinkLocalMulticast,
		Port: 5353,
	}

	AddrIPv6LinkLocalMulticast = &net.UDPAddr{
		IP:   IPv6LinkLocalMulticast,
		Port: 5353,
	}

	TTLDefault  uint32 = 75 * 60 // Default ttl for mDNS resource records
	TTLHostname uint32 = 120     // TTL for mDNS resource records containing the host name
)

// Query is a mDNS query
type Query struct {
	msg   *dns.Msg       // The query message
	iface *net.Interface // The network interface to which the message is sent
}

// Response is a mDNS response
type Response struct {
	msg   *dns.Msg       // The response message
	addr  *net.UDPAddr   // Is nil for multicast response
	iface *net.Interface // The network interface to which the message is sent
}

// Request represents an incoming mDNS message
type Request struct {
	msg   *dns.Msg       // The message
	from  *net.UDPAddr   // The source addr of the message
	iface *net.Interface // The network interface from which the message was received
}

func (r Request) String() string {
	return fmt.Sprintf("%s@%s\n%v", r.from.IP, r.iface.Name, r.msg)
}

// MDNSConn represents a mDNS connection. It encapsulates an IPv4 and IPv6 UDP connection.
type MDNSConn interface {
	// SendQuery sends a mDNS query.
	SendQuery(q *Query) error

	// SendResponse sends a mDNS response
	SendResponse(resp *Response) error

	// Read returns a channel which receives mDNS messages
	Read(ctx context.Context) <-chan *Request

	// Clears the connection buffer
	Drain(ctx context.Context)

	// Close closes the connection
	Close()
}

type mdnsConn struct {
	ipv4 *ipv4.PacketConn
	ipv6 *ipv6.PacketConn
	ch   chan *Request
}

func NewMDNSConn() (MDNSConn, error) {
	return newMDNSConn()
}

func (c *mdnsConn) SendQuery(q *Query) error {
	return c.sendQuery(q.msg, q.iface)
}

func (c *mdnsConn) SendResponse(resp *Response) error {
	if resp.addr != nil {
		return c.sendResponseTo(resp.msg, resp.iface, resp.addr)
	}

	return c.sendResponse(resp.msg, resp.iface)
}

func (c *mdnsConn) Read(ctx context.Context) <-chan *Request {
	return c.read(ctx)
}

func (c *mdnsConn) Drain(ctx context.Context) {
	log.Debug.Println("Draining connection")
	for {
		select {
		case req := <-c.Read(ctx):
			log.Debug.Println("Ignoring msg from", req.from.IP)
			break
		default:
			return
		}
	}
}

func (c *mdnsConn) Close() {
	c.close()
}

func newMDNSConn() (*mdnsConn, error) {
	var errs []error
	var connIPv4 *ipv4.PacketConn
	var connIPv6 *ipv6.PacketConn

	if conn, err := net.ListenUDP("udp4", AddrIPv4LinkLocalMulticast); err != nil {
		errs = append(errs, err)
	} else {
		connIPv4 = ipv4.NewPacketConn(conn)
		connIPv4.SetControlMessage(ipv4.FlagInterface, true)
		connIPv4.SetMulticastLoopback(false)

		for _, iface := range multicastInterfaces() {
			if err := connIPv4.JoinGroup(&iface, &net.UDPAddr{IP: IPv4LinkLocalMulticast}); err != nil {
				log.Debug.Printf("Failed joining IPv4 %v: %v", iface.Name, err)
			} else {
				log.Debug.Printf("Joined IPv4 %v", iface.Name)
			}
		}
	}

	if conn, err := net.ListenUDP("udp6", AddrIPv6LinkLocalMulticast); err != nil {
		errs = append(errs, err)
	} else {
		connIPv6 = ipv6.NewPacketConn(conn)
		connIPv6.SetControlMessage(ipv6.FlagInterface, true)
		connIPv6.SetMulticastLoopback(false)

		for _, iface := range multicastInterfaces() {
			if err := connIPv6.JoinGroup(&iface, &net.UDPAddr{IP: IPv6LinkLocalMulticast}); err != nil {
				log.Debug.Printf("Failed joining IPv6 %v: %v", iface.Name, err)
			} else {
				log.Debug.Printf("Joined IPv6 %v", iface.Name)
			}
		}
	}

	if err := first(errs...); connIPv4 == nil && connIPv6 == nil {
		return nil, fmt.Errorf("Failed setting up UDP server: %v", err)
	}

	return &mdnsConn{
		ipv4: connIPv4,
		ipv6: connIPv6,
		ch:   make(chan *Request),
	}, nil
}

func (c *mdnsConn) enableMulticastLoopback(enable bool) {
	if c.ipv4 != nil {
		c.ipv4.SetMulticastLoopback(enable)
	}

	if c.ipv6 != nil {
		c.ipv6.SetMulticastLoopback(enable)
	}
}

func (c *mdnsConn) close() {
	if c.ipv4 != nil {
		c.ipv4.Close()
	}

	if c.ipv6 != nil {
		c.ipv6.Close()
	}
}

func (c *mdnsConn) read(ctx context.Context) <-chan *Request {
	c.readInto(ctx, c.ch)
	return c.ch
}

func (c *mdnsConn) readInto(ctx context.Context, ch chan *Request) {

	isDone := func(ctx context.Context) bool {
		return ctx.Err() == context.Canceled
	}

	if c.ipv4 != nil {
		go func() {
			buf := make([]byte, 65536)
			for {
				if isDone(ctx) {
					return
				}

				n, cm, from, err := c.ipv4.ReadFrom(buf)
				if err != nil {
					continue
				}

				udpAddr, ok := from.(*net.UDPAddr)
				if !ok {
					log.Info.Println("invalid source address")
					continue
				}

				var iface *net.Interface
				if cm != nil {
					iface, err = net.InterfaceByIndex(cm.IfIndex)
					if err != nil {
						continue
					}
				}

				if n > 0 {
					m := new(dns.Msg)
					if err := m.Unpack(buf); err == nil && !shouldIgnore(m) {
						ch <- &Request{m, udpAddr, iface}
					}
				}
			}
		}()
	}

	if c.ipv6 != nil {
		go func() {
			buf := make([]byte, 65536)
			for {
				if isDone(ctx) {
					return
				}

				n, cm, from, err := c.ipv6.ReadFrom(buf)
				if err != nil {
					continue
				}

				udpAddr, ok := from.(*net.UDPAddr)
				if !ok {
					log.Info.Println("invalid source address")
					continue
				}

				var iface *net.Interface
				if cm != nil {
					iface, err = net.InterfaceByIndex(cm.IfIndex)
					if err != nil {
						continue
					}
				}

				if n > 0 {
					m := new(dns.Msg)
					if err := m.Unpack(buf); err == nil && !shouldIgnore(m) {
						ch <- &Request{m, udpAddr, iface}
					}
				}
			}
		}()
	}
}

func (c *mdnsConn) sendQuery(m *dns.Msg, iface *net.Interface) error {
	sanitizeQuery(m)

	return c.writeMsg(m, iface)
}

func (c *mdnsConn) sendResponse(m *dns.Msg, iface *net.Interface) error {
	sanitizeResponse(m)

	return c.writeMsg(m, iface)
}

func (c *mdnsConn) sendResponseTo(m *dns.Msg, iface *net.Interface, addr *net.UDPAddr) error {
	sanitizeResponse(m)

	return c.writeMsgTo(m, iface, addr)
}

func (c *mdnsConn) writeMsg(m *dns.Msg, iface *net.Interface) error {
	var err error
	if c.ipv4 != nil {
		err = c.writeMsgTo(m, iface, AddrIPv4LinkLocalMulticast)
	}

	if c.ipv6 != nil {
		err = c.writeMsgTo(m, iface, AddrIPv6LinkLocalMulticast)
	}

	return err
}

func (c *mdnsConn) writeMsgTo(m *dns.Msg, iface *net.Interface, addr *net.UDPAddr) error {
	sanitizeMsg(m)

	var err error
	if c.ipv4 != nil && addr.IP.To4() != nil {
		if out, err := m.Pack(); err == nil {
			ctrl := &ipv4.ControlMessage{}
			if iface != nil {
				ctrl.IfIndex = iface.Index
			}
			_, err = c.ipv4.WriteTo(out, ctrl, addr)
		}
	}

	if c.ipv6 != nil && addr.IP.To4() == nil {
		if out, err := m.Pack(); err == nil {
			ctrl := &ipv6.ControlMessage{}
			if iface != nil {
				ctrl.IfIndex = iface.Index
			}
			_, err = c.ipv6.WriteTo(out, ctrl, addr)
		}
	}

	return err
}

func shouldIgnore(m *dns.Msg) bool {
	if m.Opcode != 0 {
		return true
	}

	if m.Rcode != 0 {
		return true
	}

	return false
}

func sanitizeResponse(m *dns.Msg) {
	if m.Question != nil && len(m.Question) > 0 {
		log.Info.Println("Multicast DNS responses MUST NOT contain any questions in the Question Section.  (RFC6762 6)")
		m.Question = nil
	}

	if !m.Response {
		log.Info.Println("In response messages the QR bit MUST be one (RFC6762 18.2)")
		m.Response = true
	}

	if !m.Authoritative {
		log.Info.Println("AA Bit bit MUST be set to one in response messages (RFC6762 18.4)")
		m.Authoritative = true
	}

	if m.Truncated {
		log.Info.Println("In multicast response messages, the TC bit MUST be zero on transmission. (RFC6762 18.5)")
		m.Truncated = false
	}
}

func sanitizeQuery(m *dns.Msg) {
	if m.Response {
		log.Info.Println("In query messages the QR bit MUST be zero (RFC6762 18.2)")
		m.Response = false
	}

	if m.Authoritative {
		log.Info.Println("AA Bit MUST be zero in query messages (RFC6762 18.4)")
		m.Authoritative = false
	}
}

func sanitizeMsg(m *dns.Msg) {
	if m.Opcode != 0 {
		log.Info.Println("In both multicast query and multicast response messages, the OPCODE MUST be zero on transmission (RFC6762 18.3)")
		m.Opcode = 0
	}

	if m.RecursionDesired {
		log.Info.Println("In both multicast query and multicast response messages, the Recursion Available bit MUST be zero on transmission. (RFC6762 18.7)")
		m.RecursionDesired = false
	}

	if m.Zero {
		log.Info.Println("In both query and response messages, the Zero bit MUST be zero on transmission (RFC6762 18.8)")
		m.Zero = false
	}

	if m.AuthenticatedData {
		log.Info.Println("In both multicast query and multicast response messages, the Authentic Data bit MUST be zero on transmission (RFC6762 18.9)")
		m.AuthenticatedData = false
	}

	if m.CheckingDisabled {
		log.Info.Println("In both multicast query and multicast response messages, the Checking Disabled bit MUST be zero on transmission (RFC6762 18.10)")
		m.CheckingDisabled = false
	}

	if m.Rcode != 0 {
		log.Info.Println("In both multicast query and multicast response messages, the Response Code MUST be zero on transmission. (RFC6762 18.11)")
		m.Rcode = 0
	}
}

func first(errs ...error) error {
	for _, err := range errs {
		if err != nil {
			return err
		}
	}

	return nil
}

// Sets the Top Bit of rrclass for all answer records (except PTR) to trigger a cache flush in the receivers.
func setAnswerCacheFlushBit(msg *dns.Msg) {
	// From RFC6762
	//    The most significant bit of the rrclass for a record in the Answer
	//    Section of a response message is the Multicast DNS cache-flush bit
	//    and is discussed in more detail below in Section 10.2, "Announcements
	//    to Flush Outdated Cache Entries".
	for _, a := range msg.Answer {
		switch a.(type) {
		case *dns.PTR:
			continue
		default:
			a.Header().Class |= (1 << 15)
		}
	}
}

// Sets the Top Bit of class to indicate the unicast responses are preferred for this question.
func setQuestionUnicast(q *dns.Question) {
	q.Qclass |= (1 << 15)
}

// Returns true if q requires unicast responses.
func isUnicastQuestion(q dns.Question) bool {
	// From RFC6762
	// 18.12.  Repurposing of Top Bit of qclass in Question Section
	//
	//    In the Question Section of a Multicast DNS query, the top bit of the
	//    qclass field is used to indicate that unicast responses are preferred
	//    for this particular question.  (See Section 5.4.)
	return q.Qclass&(1<<15) != 0
}
<|endoftext|>


<commit_before>package mdns

// Advertise network services via multicast DNS

import (
	"errors"
	"log"
	"net"

	"dns"
	"github.com/davecheney/gmx"
)

var (
	ipv4mcastaddr = &net.UDPAddr{
		IP:   net.ParseIP("224.0.0.251"),
		Port: 5353,
	}

	ipv6mcastaddr = &net.UDPAddr{
		IP:   net.ParseIP("ff02::fb"),
		Port: 5353,
	}
	local *zone // the local mdns zone
)

func init() {
	local = &zone{
		entries: make(map[string]entries),
		add:     make(chan *entry, 16),
		queries: make(chan *query, 16),
	}
	go local.mainloop()
	if err := local.listen(ipv4mcastaddr); err != nil {
		log.Fatalf("Failed to listen %s: %s", ipv4mcastaddr, err)
	}
	if err := local.listen(ipv6mcastaddr); err != nil {
		log.Printf("Failed to listen %s: %s", ipv6mcastaddr, err)
	}

	// publish gmx stats for the local zone
	gmx.Publish("mdns.zone.local.queries", func() interface{} {
		return local.queryCount
	})
	gmx.Publish("mdns.zone.local.entries", func() interface{} {
		return local.entryCount
	})

}

// Publish adds a record, described in RFC XXX
func Publish(r string) error {
	rr, err := dns.NewRR(r)
	if err != nil {
		return err
	}
	local.add <- &entry{rr}
	return nil
}

type entry struct {
	dns.RR
}

func (e *entry) fqdn() string {
	return e.Header().Name
}

type query struct {
	dns.Question
	result chan *entry
}

type entries []*entry

func (e entries) contains(entry *entry) bool {
	for _, ee := range e {
		if equals(entry, ee) {
			return true
		}
	}
	return false
}

type zone struct {
	entries map[string]entries
	add     chan *entry // add entries to zone
	queries chan *query // query exsting entries in zone

	// gmx statistics
	queryCount, entryCount int
}

func (z *zone) mainloop() {
	for {
		select {
		case entry := <-z.add:
			z.entryCount++
			if !z.entries[entry.fqdn()].contains(entry) {
				z.entries[entry.fqdn()] = append(z.entries[entry.fqdn()], entry)
			}
		case q := <-z.queries:
			z.queryCount++
			for _, entry := range z.entries[q.Question.Name] {
				if q.matches(entry) {
					q.result <- entry
				}
			}
			close(q.result)
		}
	}
}

func (z *zone) query(q dns.Question) (entries []*entry) {
	res := make(chan *entry, 16)
	z.queries <- &query{q, res}
	for e := range res {
		entries = append(entries, e)
	}
	return
}

func (q *query) matches(entry *entry) bool {
	return q.Question.Qtype == dns.TypeANY || q.Question.Qtype == entry.RR.Header().Rrtype
}

func equals(this, that *entry) bool {
	if _, ok := this.RR.(*dns.RR_ANY); ok {
		return true // *RR_ANY matches anything
	}
	if _, ok := that.RR.(*dns.RR_ANY); ok {
		return true // *RR_ANY matches all
	}
	return false
}

type connector struct {
	*net.UDPAddr
	*net.UDPConn
	*zone

	// gmx statistics
	questions, responses int
}

func (z *zone) listen(addr *net.UDPAddr) error {
	conn, err := openSocket(addr)
	if err != nil {
		return err
	}
	c := &connector{
		UDPAddr: addr,
		UDPConn: conn,
		zone:    z,
	}
	go c.mainloop()

	// publish gmx stats for the connector
	gmx.Publish("mdns.connector.questions", func() interface{} {
		return c.questions
	})
	gmx.Publish("mdns.connector.responses", func() interface{} {
		return c.responses
	})

	return nil
}

func openSocket(addr *net.UDPAddr) (*net.UDPConn, error) {
	switch addr.IP.To4() {
	case nil:
		return net.ListenMulticastUDP("udp6", nil, ipv6mcastaddr)
	default:
		return net.ListenMulticastUDP("udp4", nil, ipv4mcastaddr)
	}
	panic("unreachable")
}

type pkt struct {
	*dns.Msg
	*net.UDPAddr
}

func (c *connector) readloop(in chan pkt) {
	for {
		msg, addr, err := c.readMessage()
		if err != nil {
			// log dud packets
			log.Printf("Cound not read from %s: %s", c.UDPConn, err)
		}
		if msg.IsQuestion() {
			c.questions++
			in <- pkt{msg, addr}
		}
	}
}

func (c *connector) mainloop() {
	in := make(chan pkt, 32)
	go c.readloop(in)
	for {
		msg := <-in
		msg.MsgHdr.Response = true // convert question to response
		for _, result := range c.query(msg.Question) {
			msg.Answer = append(msg.Answer, result.RR)
		}
		msg.Extra = append(msg.Extra, c.findExtra(msg.Answer...)...)
		if len(msg.Answer) > 0 {
			c.responses++
			// nuke questions
			msg.Question = nil
			if err := c.writeMessage(msg.Msg); err != nil {
				log.Fatalf("Cannot send: %s", err)
			}
		}
	}
}

func (c *connector) query(qs []dns.Question) (results []*entry) {
	for _, q := range qs {
		results = append(results, c.zone.query(q)...)
	}
	return
}

// recursively probe for related records
func (c *connector) findExtra(r ...dns.RR) (extra []dns.RR) {
	for _, rr := range r {
		var q dns.Question
		switch rr := rr.(type) {
		case *dns.RR_PTR:
			q = dns.Question{
				Name:   rr.Ptr,
				Qtype:  dns.TypeANY,
				Qclass: dns.ClassINET,
			}
		case *dns.RR_SRV:
			q = dns.Question{
				Name:   rr.Target,
				Qtype:  dns.TypeA,
				Qclass: dns.ClassINET,
			}
		default:
			continue
		}
		res := c.zone.query(q)
		if len(res) > 0 {
			for _, entry := range res {
				extra = append(append(extra, entry.RR), c.findExtra(entry.RR)...)
			}
		}
	}
	return
}

// encode an mdns msg and broadcast it on the wire
func (c *connector) writeMessage(msg *dns.Msg) (err error) {
	if buf, ok := msg.Pack(); ok {
		_, err = c.WriteToUDP(buf, c.UDPAddr)
	}
	return
}

// consume an mdns packet from the wire and decode it
func (c *connector) readMessage() (*dns.Msg, *net.UDPAddr, error) {
	buf := make([]byte, 1500)
	read, addr, err := c.ReadFromUDP(buf)
	if err != nil {
		return nil, nil, err
	}
	if msg := new(dns.Msg); msg.Unpack(buf[:read]) {
		return msg, addr, nil
	}
	return nil, addr, errors.New("Unable to unpack buffer")
}
<commit_msg>Fixed external package reference<commit_after>package mdns

// Advertise network services via multicast DNS

import (
	"errors"
	"log"
	"net"

	"github.com/miekg/dns"
	"github.com/davecheney/gmx"
)

var (
	ipv4mcastaddr = &net.UDPAddr{
		IP:   net.ParseIP("224.0.0.251"),
		Port: 5353,
	}

	ipv6mcastaddr = &net.UDPAddr{
		IP:   net.ParseIP("ff02::fb"),
		Port: 5353,
	}
	local *zone // the local mdns zone
)

func init() {
	local = &zone{
		entries: make(map[string]entries),
		add:     make(chan *entry, 16),
		queries: make(chan *query, 16),
	}
	go local.mainloop()
	if err := local.listen(ipv4mcastaddr); err != nil {
		log.Fatalf("Failed to listen %s: %s", ipv4mcastaddr, err)
	}
	if err := local.listen(ipv6mcastaddr); err != nil {
		log.Printf("Failed to listen %s: %s", ipv6mcastaddr, err)
	}

	// publish gmx stats for the local zone
	gmx.Publish("mdns.zone.local.queries", func() interface{} {
		return local.queryCount
	})
	gmx.Publish("mdns.zone.local.entries", func() interface{} {
		return local.entryCount
	})

}

// Publish adds a record, described in RFC XXX
func Publish(r string) error {
	rr, err := dns.NewRR(r)
	if err != nil {
		return err
	}
	local.add <- &entry{rr}
	return nil
}

type entry struct {
	dns.RR
}

func (e *entry) fqdn() string {
	return e.Header().Name
}

type query struct {
	dns.Question
	result chan *entry
}

type entries []*entry

func (e entries) contains(entry *entry) bool {
	for _, ee := range e {
		if equals(entry, ee) {
			return true
		}
	}
	return false
}

type zone struct {
	entries map[string]entries
	add     chan *entry // add entries to zone
	queries chan *query // query exsting entries in zone

	// gmx statistics
	queryCount, entryCount int
}

func (z *zone) mainloop() {
	for {
		select {
		case entry := <-z.add:
			z.entryCount++
			if !z.entries[entry.fqdn()].contains(entry) {
				z.entries[entry.fqdn()] = append(z.entries[entry.fqdn()], entry)
			}
		case q := <-z.queries:
			z.queryCount++
			for _, entry := range z.entries[q.Question.Name] {
				if q.matches(entry) {
					q.result <- entry
				}
			}
			close(q.result)
		}
	}
}

func (z *zone) query(q dns.Question) (entries []*entry) {
	res := make(chan *entry, 16)
	z.queries <- &query{q, res}
	for e := range res {
		entries = append(entries, e)
	}
	return
}

func (q *query) matches(entry *entry) bool {
	return q.Question.Qtype == dns.TypeANY || q.Question.Qtype == entry.RR.Header().Rrtype
}

func equals(this, that *entry) bool {
	if _, ok := this.RR.(*dns.RR_ANY); ok {
		return true // *RR_ANY matches anything
	}
	if _, ok := that.RR.(*dns.RR_ANY); ok {
		return true // *RR_ANY matches all
	}
	return false
}

type connector struct {
	*net.UDPAddr
	*net.UDPConn
	*zone

	// gmx statistics
	questions, responses int
}

func (z *zone) listen(addr *net.UDPAddr) error {
	conn, err := openSocket(addr)
	if err != nil {
		return err
	}
	c := &connector{
		UDPAddr: addr,
		UDPConn: conn,
		zone:    z,
	}
	go c.mainloop()

	// publish gmx stats for the connector
	gmx.Publish("mdns.connector.questions", func() interface{} {
		return c.questions
	})
	gmx.Publish("mdns.connector.responses", func() interface{} {
		return c.responses
	})

	return nil
}

func openSocket(addr *net.UDPAddr) (*net.UDPConn, error) {
	switch addr.IP.To4() {
	case nil:
		return net.ListenMulticastUDP("udp6", nil, ipv6mcastaddr)
	default:
		return net.ListenMulticastUDP("udp4", nil, ipv4mcastaddr)
	}
	panic("unreachable")
}

type pkt struct {
	*dns.Msg
	*net.UDPAddr
}

func (c *connector) readloop(in chan pkt) {
	for {
		msg, addr, err := c.readMessage()
		if err != nil {
			// log dud packets
			log.Printf("Cound not read from %s: %s", c.UDPConn, err)
		}
		if msg.IsQuestion() {
			c.questions++
			in <- pkt{msg, addr}
		}
	}
}

func (c *connector) mainloop() {
	in := make(chan pkt, 32)
	go c.readloop(in)
	for {
		msg := <-in
		msg.MsgHdr.Response = true // convert question to response
		for _, result := range c.query(msg.Question) {
			msg.Answer = append(msg.Answer, result.RR)
		}
		msg.Extra = append(msg.Extra, c.findExtra(msg.Answer...)...)
		if len(msg.Answer) > 0 {
			c.responses++
			// nuke questions
			msg.Question = nil
			if err := c.writeMessage(msg.Msg); err != nil {
				log.Fatalf("Cannot send: %s", err)
			}
		}
	}
}

func (c *connector) query(qs []dns.Question) (results []*entry) {
	for _, q := range qs {
		results = append(results, c.zone.query(q)...)
	}
	return
}

// recursively probe for related records
func (c *connector) findExtra(r ...dns.RR) (extra []dns.RR) {
	for _, rr := range r {
		var q dns.Question
		switch rr := rr.(type) {
		case *dns.RR_PTR:
			q = dns.Question{
				Name:   rr.Ptr,
				Qtype:  dns.TypeANY,
				Qclass: dns.ClassINET,
			}
		case *dns.RR_SRV:
			q = dns.Question{
				Name:   rr.Target,
				Qtype:  dns.TypeA,
				Qclass: dns.ClassINET,
			}
		default:
			continue
		}
		res := c.zone.query(q)
		if len(res) > 0 {
			for _, entry := range res {
				extra = append(append(extra, entry.RR), c.findExtra(entry.RR)...)
			}
		}
	}
	return
}

// encode an mdns msg and broadcast it on the wire
func (c *connector) writeMessage(msg *dns.Msg) (err error) {
	if buf, ok := msg.Pack(); ok {
		_, err = c.WriteToUDP(buf, c.UDPAddr)
	}
	return
}

// consume an mdns packet from the wire and decode it
func (c *connector) readMessage() (*dns.Msg, *net.UDPAddr, error) {
	buf := make([]byte, 1500)
	read, addr, err := c.ReadFromUDP(buf)
	if err != nil {
		return nil, nil, err
	}
	if msg := new(dns.Msg); msg.Unpack(buf[:read]) {
		return msg, addr, nil
	}
	return nil, addr, errors.New("Unable to unpack buffer")
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/jessevdk/go-flags"
	"os"
	"path/filepath"
	"strings"
	"time"
)

var opts struct {
	Verbose           []bool   `short:"v" long:"verbose" description:"Show verbose debug information"`
	ArtifactIds       []string `short:"i" long:"artifact-id" description:"An artifact id"`
	ArtifactVersions  []string `short:"v" long:"artifact-version" description:"An artifact version"`
	ArtifactLocations []string `short:"l" long:"artifact-location" description:"An artifact location"`
	Cookbooks         []string `short:"c" long:"cookbook" description:"A cookbook location"`
}

func main() {
	args, err := flags.Parse(&opts)

	if err != nil {
		fail(err.Error(), usageHelp)
	}

	switch command := getCommand(args, 1, "usage"); {
	case command == "help":
		handleHelp(args)
	case command == "create":
		handleCreate(args)
	case command == "show":
		handleShow(args)
	case command == "artifacts":
		handleArtifacts(args)
	case command == "cookbooks":
		handleCookbooks(args)
	case command == "list":
		handleList(args)
	default:
		usageHelp()
	}
}

func getCommand(args []string, position int, defaultCommand string) string {
	if len(args) >= position {
		return args[position-1]
	}
	return defaultCommand
}

func handleHelp(args []string) {
	switch command := getCommand(args, 2, "help"); {
	case command == "create":
		helpCreate()
	case command == "show":
		helpShow()
	case command == "cookbooks":
		helpCookbooks()
	case command == "artifacts":
		helpArtifacts()
	case command == "list":
		helpList()
	default:
		helpHelp()
	}
}

func handleCreate(args []string) {
	rules := make([]Rule, 3)
	rules[0] = Rule{len(opts.ArtifactIds) == 0, "Error: One or more artifacts must be provided."}
	rules[1] = Rule{len(opts.ArtifactIds) != len(opts.ArtifactVersions), "Error: An equal number of artifact ids, versions and locations are required."}
	rules[1] = Rule{len(opts.ArtifactVersions) != len(opts.ArtifactLocations), "Error: An equal number of artifact ids, versions and locations are required."}

	for _, rule := range rules {
		if rule.validated {
			fail(rule.message, helpCreate)
		}
	}

	artifacts := make([]Artifact, len(opts.ArtifactVersions))
	cookbooks := make([]Cookbook, len(opts.Cookbooks))

	for i := 0; i < len(opts.ArtifactIds); i++ {
		artifact := Artifact{
			Id:       opts.ArtifactIds[i],
			Version:  opts.ArtifactVersions[i],
			Location: opts.ArtifactLocations[i],
		}
		artifacts[i] = artifact
	}

	for i := 0; i < len(opts.Cookbooks); i++ {
		cookbook := Cookbook{
			Location: opts.Cookbooks[i],
		}
		cookbooks[i] = cookbook
	}

	createdAt := time.Now().Unix()

	release := Release{
		Time:      int(createdAt),
		Artifacts: artifacts,
		Cookbooks: cookbooks,
	}

	b, err := json.Marshal(release)
	if err != nil {
		fmt.Println("Error: ", err)
		os.Exit(1)
	}
	if len(opts.Verbose) > 0 {
		fmt.Println(string(b))
	}

	path := ""
	path, err = getCreatePath(release, args)
	writeFile(path, b)
}

func exists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

func getCreatePath(release Release, args []string) (string, error) {
	if len(args) > 1 {
		uriType := getPathType(args[1])
		if uriType == File {
			return scrubPath(args[1]), nil
		}
		return "", errors.New("Error: Only file URIs are supported at this time.")
	}
	cwd, err := os.Getwd()
	if err != nil {
		return "", err
	}
	fileName := fmt.Sprintf("%v.menu", release.Time)
	return filepath.Join(cwd, fileName), nil
}

type DisplayFilter int

func handleShow(args []string) {
	handleDisplay(args, 0, helpShow)
}

func handleArtifacts(args []string) {
	handleDisplay(args, 1, helpArtifacts)
}

func handleCookbooks(args []string) {
	handleDisplay(args, 2, helpCookbooks)
}

func handleDisplay(args []string, filter DisplayFilter, displayFunc help) {
	rules := make([]Rule, 1)
	rules[0] = Rule{len(args) == 1, "Error: One or more paths must be provided."}

	for _, rule := range rules {
		if rule.validated {
			fail(rule.message, displayFunc)
		}
	}

	for index, path := range unique(args) {
		if index > 0 {
			uriType := getPathType(path)
			if uriType == File {
				release, err := readFile(path)
				if err != nil {
					fail(err.Error(), displayFunc)
				}
				switch filter {
				case 0:
					release.Display()
				case 1:
					release.DisplayArtifacts()
				case 2:
					release.DisplayCookbooks()
				}
			}
		}
	}
}

func handleList(args []string) {
	rules := make([]Rule, 1)
	rules[0] = Rule{len(args) == 1, "Error: One or more paths must be provided."}

	for _, rule := range rules {
		if rule.validated {
			fail(rule.message, helpList)
		}
	}

	paths := make([]string, 0)

	for index, path := range unique(args) {
		if index > 0 {
			uriType := getPathType(path)
			if uriType == File {
				filePath := scrubPath(path)
				paths = discoverPaths(filePath, paths)
			}
		}
	}
	for _, path := range paths {
		release, err := readFile(path)
		if err != nil {
			fail(err.Error(), helpList)
		}
		if match(release) {
			release.Display()
		}
	}
}

func match(release *Release) bool {
	matches := len(opts.ArtifactVersions)
	if matches == 0 {
		return true
	}
	if len(opts.ArtifactIds) > 0 {
		if release.HasArtifactIds(opts.ArtifactIds) {
			return true
		}
	}
	if len(opts.ArtifactVersions) > 0 {
		if release.HasArtifactVersions(opts.ArtifactVersions) {
			return true
		}
	}
	if len(opts.ArtifactLocations) > 0 {
		if release.HasArtifactLocations(opts.ArtifactLocations) {
			return true
		}
	}
	return false
}

func getPathType(path string) UriType {
	if strings.HasPrefix(path, "file:///") {
		return File
	}
	if strings.HasPrefix(path, "file://localhost/") {
		return File
	}
	return Unknown
}

func unique(slice []string) []string {
	values := make([]string, 0)
	for _, element := range slice {
		values = appendIfMissing(values, element)
	}
	return values
}

func appendIfMissing(slice []string, value string) []string {
	for _, ele := range slice {
		if ele == value {
			return slice
		}
	}
	return append(slice, value)
}

func Contains(list []interface{}, elem interface{}) bool {
	for _, t := range list {
		if t == elem {
			return true
		}
	}
	return false
}
<commit_msg>Removing unused call.<commit_after>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/jessevdk/go-flags"
	"os"
	"path/filepath"
	"strings"
	"time"
)

var opts struct {
	Verbose           []bool   `short:"v" long:"verbose" description:"Show verbose debug information"`
	ArtifactIds       []string `short:"i" long:"artifact-id" description:"An artifact id"`
	ArtifactVersions  []string `short:"v" long:"artifact-version" description:"An artifact version"`
	ArtifactLocations []string `short:"l" long:"artifact-location" description:"An artifact location"`
	Cookbooks         []string `short:"c" long:"cookbook" description:"A cookbook location"`
}

func main() {
	args, err := flags.Parse(&opts)

	if err != nil {
		fail(err.Error(), usageHelp)
	}

	switch command := getCommand(args, 1, "usage"); {
	case command == "help":
		handleHelp(args)
	case command == "create":
		handleCreate(args)
	case command == "show":
		handleShow(args)
	case command == "artifacts":
		handleArtifacts(args)
	case command == "cookbooks":
		handleCookbooks(args)
	case command == "list":
		handleList(args)
	default:
		usageHelp()
	}
}

func getCommand(args []string, position int, defaultCommand string) string {
	if len(args) >= position {
		return args[position-1]
	}
	return defaultCommand
}

func handleHelp(args []string) {
	switch command := getCommand(args, 2, "help"); {
	case command == "create":
		helpCreate()
	case command == "show":
		helpShow()
	case command == "cookbooks":
		helpCookbooks()
	case command == "artifacts":
		helpArtifacts()
	case command == "list":
		helpList()
	default:
		helpHelp()
	}
}

func handleCreate(args []string) {
	rules := make([]Rule, 3)
	rules[0] = Rule{len(opts.ArtifactIds) == 0, "Error: One or more artifacts must be provided."}
	rules[1] = Rule{len(opts.ArtifactIds) != len(opts.ArtifactVersions), "Error: An equal number of artifact ids, versions and locations are required."}
	rules[1] = Rule{len(opts.ArtifactVersions) != len(opts.ArtifactLocations), "Error: An equal number of artifact ids, versions and locations are required."}

	for _, rule := range rules {
		if rule.validated {
			fail(rule.message, helpCreate)
		}
	}

	artifacts := make([]Artifact, len(opts.ArtifactVersions))
	cookbooks := make([]Cookbook, len(opts.Cookbooks))

	for i := 0; i < len(opts.ArtifactIds); i++ {
		artifact := Artifact{
			Id:       opts.ArtifactIds[i],
			Version:  opts.ArtifactVersions[i],
			Location: opts.ArtifactLocations[i],
		}
		artifacts[i] = artifact
	}

	for i := 0; i < len(opts.Cookbooks); i++ {
		cookbook := Cookbook{
			Location: opts.Cookbooks[i],
		}
		cookbooks[i] = cookbook
	}

	createdAt := time.Now().Unix()

	release := Release{
		Time:      int(createdAt),
		Artifacts: artifacts,
		Cookbooks: cookbooks,
	}

	b, err := json.Marshal(release)
	if err != nil {
		fmt.Println("Error: ", err)
		os.Exit(1)
	}
	if len(opts.Verbose) > 0 {
		fmt.Println(string(b))
	}

	path := ""
	path, err = getCreatePath(release, args)
	writeFile(path, b)
}

func exists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

func getCreatePath(release Release, args []string) (string, error) {
	if len(args) > 1 {
		uriType := getPathType(args[1])
		if uriType == File {
			return scrubPath(args[1]), nil
		}
		return "", errors.New("Error: Only file URIs are supported at this time.")
	}
	cwd, err := os.Getwd()
	if err != nil {
		return "", err
	}
	fileName := fmt.Sprintf("%v.menu", release.Time)
	return filepath.Join(cwd, fileName), nil
}

type DisplayFilter int

func handleShow(args []string) {
	handleDisplay(args, 0, helpShow)
}

func handleArtifacts(args []string) {
	handleDisplay(args, 1, helpArtifacts)
}

func handleCookbooks(args []string) {
	handleDisplay(args, 2, helpCookbooks)
}

func handleDisplay(args []string, filter DisplayFilter, displayFunc help) {
	rules := make([]Rule, 1)
	rules[0] = Rule{len(args) == 1, "Error: One or more paths must be provided."}

	for _, rule := range rules {
		if rule.validated {
			fail(rule.message, displayFunc)
		}
	}

	for index, path := range unique(args) {
		if index > 0 {
			uriType := getPathType(path)
			if uriType == File {
				release, err := readFile(path)
				if err != nil {
					fail(err.Error(), displayFunc)
				}
				switch filter {
				case 0:
					release.Display()
				case 1:
					release.DisplayArtifacts()
				case 2:
					release.DisplayCookbooks()
				}
			}
		}
	}
}

func handleList(args []string) {
	rules := make([]Rule, 1)
	rules[0] = Rule{len(args) == 1, "Error: One or more paths must be provided."}

	for _, rule := range rules {
		if rule.validated {
			fail(rule.message, helpList)
		}
	}

	paths := make([]string, 0)

	for _, path := range unique(args[1:]) {
		uriType := getPathType(path)
		if uriType == File {
			filePath := scrubPath(path)
			paths = discoverPaths(filePath, paths)
		}
	}
	for _, path := range paths {
		release, err := readFile(path)
		if err != nil {
			fail(err.Error(), helpList)
		}
		if match(release) {
			release.Display()
		}
	}
}

func match(release *Release) bool {
	matches := len(opts.ArtifactVersions)
	if matches == 0 {
		return true
	}
	if len(opts.ArtifactIds) > 0 {
		if release.HasArtifactIds(opts.ArtifactIds) {
			return true
		}
	}
	if len(opts.ArtifactVersions) > 0 {
		if release.HasArtifactVersions(opts.ArtifactVersions) {
			return true
		}
	}
	if len(opts.ArtifactLocations) > 0 {
		if release.HasArtifactLocations(opts.ArtifactLocations) {
			return true
		}
	}
	return false
}

func getPathType(path string) UriType {
	if strings.HasPrefix(path, "file:///") {
		return File
	}
	if strings.HasPrefix(path, "file://localhost/") {
		return File
	}
	return Unknown
}

func unique(slice []string) []string {
	values := make([]string, 0)
	for _, element := range slice {
		values = appendIfMissing(values, element)
	}
	return values
}

func appendIfMissing(slice []string, value string) []string {
	for _, ele := range slice {
		if ele == value {
			return slice
		}
	}
	return append(slice, value)
}
<|endoftext|>