<commit_before>// Copyright 2018 Solly Ross
//
// 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 zapr defines an implementation of the github.com/go-logr/logr
// interfaces built on top of Zap (go.uber.org/zap).
//
// Usage
//
// A new logr.Logger can be constructed from an existing zap.Logger using
// the NewLogger function:
//
//  log := zapr.NewLogger(someZapLogger)
//
// Implementation Details
//
// For the most part, concepts in Zap correspond directly with those in
// logr.
//
// Unlike Zap, all fields *must* be in the form of suggared fields --
// it's illegal to pass a strongly-typed Zap field in a key position
// to any of the log methods.
//
// Levels in logr correspond to custom debug levels in Zap.  Any given level
// in logr is represents by its inverse in zap (`zapLevel = -1*logrLevel`).
// For example V(2) is equivalent to log level -2 in Zap, while V(1) is
// equivalent to Zap's DebugLevel.
package zapr

import (
	"github.com/go-logr/logr"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

// noopInfoLogger is a logr.InfoLogger that's always disabled, and does nothing.
type noopInfoLogger struct{}

func (l *noopInfoLogger) Enabled() bool                   { return false }
func (l *noopInfoLogger) Info(_ string, _ ...interface{}) {}

var disabledInfoLogger = &noopInfoLogger{}

// NB: right now, we always use the equivalent of sugared logging.
// This is necessary, since logr doesn't define non-suggared types,
// and using zap-specific non-suggared types would make uses tied
// directly to Zap.

// infoLogger is a logr.InfoLogger that uses Zap to log at a particular
// level.  The level has already been converted to a Zap level, which
// is to say that `logrLevel = -1*zapLevel`.
type infoLogger struct {
	lvl zapcore.Level
	l   *zap.Logger
}

func (l *infoLogger) Enabled() bool { return true }
func (l *infoLogger) Info(msg string, keysAndVals ...interface{}) {
	if checkedEntry := l.l.Check(l.lvl, msg); checkedEntry != nil {
		checkedEntry.Write(handleFields(l.l, keysAndVals)...)
	}
}

// zapLogger is a logr.Logger that uses Zap to log.
type zapLogger struct {
	// NB: this looks very similar to zap.SugaredLogger, but
	// deals with our desire to have multiple verbosity levels.
	l *zap.Logger
	infoLogger
}

// handleFields converts a bunch of arbitrary key-value pairs into Zap fields.  It takes
// additional pre-converted Zap fields, for use with automatically attached fields, like
// `error`.
func handleFields(l *zap.Logger, args []interface{}, additional ...zap.Field) []zap.Field {
	// a slightly modified version of zap.SugaredLogger.sweetenFields
	if len(args) == 0 {
		// fast-return if we have no suggared fields.
		return additional
	}

	// unlike Zap, we can be pretty sure users aren't passing structured
	// fields (since logr has no concept of that), so guess that we need a
	// little less space.
	fields := make([]zap.Field, 0, len(args)/2+len(additional))
	for i := 0; i < len(args); {
		// check just in case for strongly-typed Zap fields, which is illegal (since
		// it breaks implementation agnosticism), so we can give a better error message.
		if _, ok := args[i].(zap.Field); ok {
			l.DPanic("strongly-typed Zap Field passed to logr", zap.Any("zap field", args[i]))
			break
		}

		// make sure this isn't a mismatched key
		if i == len(args)-1 {
			l.DPanic("odd number of arguments passed as key-value pairs for logging", zap.Any("ignored key", args[i]))
			break
		}

		// process a key-value pair,
		// ensuring that the key is a string
		key, val := args[i], args[i+1]
		keyStr, isString := key.(string)
		if !isString {
			// if the key isn't a string, DPanic and stop logging
			l.DPanic("non-string key argument passed to logging, ignoring all later arguments", zap.Any("invalid key", key))
			break
		}

		fields = append(fields, zap.Any(keyStr, val))
		i += 2
	}

	return append(fields, additional...)
}

func (l *zapLogger) Error(err error, msg string, keysAndVals ...interface{}) {
	if checkedEntry := l.l.Check(zap.ErrorLevel, msg); checkedEntry != nil {
		checkedEntry.Write(handleFields(l.l, keysAndVals, zap.Error(err))...)
	}
}

func (l *zapLogger) V(level int) logr.InfoLogger {
	lvl := zapcore.Level(-1 * level)
	if l.l.Core().Enabled(lvl) {
		return &infoLogger{
			lvl: lvl,
			l:   l.l,
		}
	}
	return disabledInfoLogger
}

func (l *zapLogger) WithValues(keysAndValues ...interface{}) logr.Logger {
	newLogger := l.l.With(handleFields(l.l, keysAndValues)...)
	return NewLogger(newLogger)
}

func (l *zapLogger) WithName(name string) logr.Logger {
	newLogger := l.l.Named(name)
	return NewLogger(newLogger)
}

// NewLogger creates a new logr.Logger using the given Zap Logger to log.
func NewLogger(l *zap.Logger) logr.Logger {
	log := l.WithOptions(zap.AddCallerSkip(1))
	return &zapLogger{
		l: log,
		infoLogger: infoLogger{
			l:   log,
			lvl: zap.InfoLevel,
		},
	}
}
<commit_msg>Fix caller for loggers created WithName or WithValues<commit_after>// Copyright 2018 Solly Ross
//
// 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 zapr defines an implementation of the github.com/go-logr/logr
// interfaces built on top of Zap (go.uber.org/zap).
//
// Usage
//
// A new logr.Logger can be constructed from an existing zap.Logger using
// the NewLogger function:
//
//  log := zapr.NewLogger(someZapLogger)
//
// Implementation Details
//
// For the most part, concepts in Zap correspond directly with those in
// logr.
//
// Unlike Zap, all fields *must* be in the form of suggared fields --
// it's illegal to pass a strongly-typed Zap field in a key position
// to any of the log methods.
//
// Levels in logr correspond to custom debug levels in Zap.  Any given level
// in logr is represents by its inverse in zap (`zapLevel = -1*logrLevel`).
// For example V(2) is equivalent to log level -2 in Zap, while V(1) is
// equivalent to Zap's DebugLevel.
package zapr

import (
	"github.com/go-logr/logr"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

// noopInfoLogger is a logr.InfoLogger that's always disabled, and does nothing.
type noopInfoLogger struct{}

func (l *noopInfoLogger) Enabled() bool                   { return false }
func (l *noopInfoLogger) Info(_ string, _ ...interface{}) {}

var disabledInfoLogger = &noopInfoLogger{}

// NB: right now, we always use the equivalent of sugared logging.
// This is necessary, since logr doesn't define non-suggared types,
// and using zap-specific non-suggared types would make uses tied
// directly to Zap.

// infoLogger is a logr.InfoLogger that uses Zap to log at a particular
// level.  The level has already been converted to a Zap level, which
// is to say that `logrLevel = -1*zapLevel`.
type infoLogger struct {
	lvl zapcore.Level
	l   *zap.Logger
}

func (l *infoLogger) Enabled() bool { return true }
func (l *infoLogger) Info(msg string, keysAndVals ...interface{}) {
	if checkedEntry := l.l.Check(l.lvl, msg); checkedEntry != nil {
		checkedEntry.Write(handleFields(l.l, keysAndVals)...)
	}
}

// zapLogger is a logr.Logger that uses Zap to log.
type zapLogger struct {
	// NB: this looks very similar to zap.SugaredLogger, but
	// deals with our desire to have multiple verbosity levels.
	l *zap.Logger
	infoLogger
}

// handleFields converts a bunch of arbitrary key-value pairs into Zap fields.  It takes
// additional pre-converted Zap fields, for use with automatically attached fields, like
// `error`.
func handleFields(l *zap.Logger, args []interface{}, additional ...zap.Field) []zap.Field {
	// a slightly modified version of zap.SugaredLogger.sweetenFields
	if len(args) == 0 {
		// fast-return if we have no suggared fields.
		return additional
	}

	// unlike Zap, we can be pretty sure users aren't passing structured
	// fields (since logr has no concept of that), so guess that we need a
	// little less space.
	fields := make([]zap.Field, 0, len(args)/2+len(additional))
	for i := 0; i < len(args); {
		// check just in case for strongly-typed Zap fields, which is illegal (since
		// it breaks implementation agnosticism), so we can give a better error message.
		if _, ok := args[i].(zap.Field); ok {
			l.DPanic("strongly-typed Zap Field passed to logr", zap.Any("zap field", args[i]))
			break
		}

		// make sure this isn't a mismatched key
		if i == len(args)-1 {
			l.DPanic("odd number of arguments passed as key-value pairs for logging", zap.Any("ignored key", args[i]))
			break
		}

		// process a key-value pair,
		// ensuring that the key is a string
		key, val := args[i], args[i+1]
		keyStr, isString := key.(string)
		if !isString {
			// if the key isn't a string, DPanic and stop logging
			l.DPanic("non-string key argument passed to logging, ignoring all later arguments", zap.Any("invalid key", key))
			break
		}

		fields = append(fields, zap.Any(keyStr, val))
		i += 2
	}

	return append(fields, additional...)
}

func (l *zapLogger) Error(err error, msg string, keysAndVals ...interface{}) {
	if checkedEntry := l.l.Check(zap.ErrorLevel, msg); checkedEntry != nil {
		checkedEntry.Write(handleFields(l.l, keysAndVals, zap.Error(err))...)
	}
}

func (l *zapLogger) V(level int) logr.InfoLogger {
	lvl := zapcore.Level(-1 * level)
	if l.l.Core().Enabled(lvl) {
		return &infoLogger{
			lvl: lvl,
			l:   l.l,
		}
	}
	return disabledInfoLogger
}

func (l *zapLogger) WithValues(keysAndValues ...interface{}) logr.Logger {
	newLogger := l.l.With(handleFields(l.l, keysAndValues)...)
	return internalNewLogger(newLogger, 0)
}

func (l *zapLogger) WithName(name string) logr.Logger {
	newLogger := l.l.Named(name)
	return internalNewLogger(newLogger, 0)
}

// internalNewLogger allows creation of loggers with variable levels of callstack skipping
func internalNewLogger(l *zap.Logger, callerSkip int) logr.Logger {
	log := l.WithOptions(zap.AddCallerSkip(callerSkip))
	return &zapLogger{
		l: log,
		infoLogger: infoLogger{
			l:   log,
			lvl: zap.InfoLevel,
		},
	}
}

// NewLogger creates a new logr.Logger using the given Zap Logger to log.
func NewLogger(l *zap.Logger) logr.Logger {
	// creates a new logger skipping one level of callstack
	return internalNewLogger(l, 1)
}
<|endoftext|>


<commit_before>package mdns

import (
	"fmt"
	"net"
	"os"
	"strings"

	"github.com/miekg/dns"
)

const (
	// defaultTTL is the default TTL value in returned DNS records in seconds.
	defaultTTL = 120
)

// Zone is the interface used to integrate with the server and
// to serve records dynamically
type Zone interface {
	// Records returns DNS records in response to a DNS question.
	Records(q dns.Question) []dns.RR
}

// MDNSService is used to export a named service by implementing a Zone
type MDNSService struct {
	Instance string   // Instance name (e.g. "hostService name")
	Service  string   // Service name (e.g. "_http._tcp.")
	Domain   string   // If blank, assumes "local"
	HostName string   // Host machine DNS name (e.g. "mymachine.net.")
	Port     int      // Service Port
	IPs      []net.IP // IP addresses for the service's host
	TXT      []string // Service TXT records

	serviceAddr  string // Fully qualified service address
	instanceAddr string // Fully qualified instance address
	enumAddr     string // _services._dns-sd._udp.<domain>
}

// validateFQDN returns an error if the passed string is not a fully qualified
// hdomain name (more specifically, a hostname).
func validateFQDN(s string) error {
	if len(s) == 0 {
		return fmt.Errorf("FQDN must not be blank")
	}
	if s[len(s)-1] != '.' {
		return fmt.Errorf("FQDN must end in period: %s", s)
	}
	// TODO(reddaly): Perform full validation.

	return nil
}

// NewMDNSService returns a new instance of MDNSService.
//
// If domain, hostName, or ips is set to the zero value, then a default value
// will be inferred from the operating system.
//
// TODO(reddaly): This interface may need to change to account for "unique
// record" conflict rules of the mDNS protocol.  Upon startup, the server should
// check to ensure that the instance name does not conflict with other instance
// names, and, if required, select a new name.  There may also be conflicting
// hostName A/AAAA records.
func NewMDNSService(instance, service, domain, hostName string, port int, ips []net.IP, txt []string) (*MDNSService, error) {
	// Sanity check inputs
	if instance == "" {
		return nil, fmt.Errorf("missing service instance name")
	}
	if service == "" {
		return nil, fmt.Errorf("missing service name")
	}
	if port == 0 {
		return nil, fmt.Errorf("missing service port")
	}

	// Set default domain
	if domain == "" {
		domain = "local."
	}
	if err := validateFQDN(domain); err != nil {
		return nil, fmt.Errorf("domain %q is not a fully-qualified domain name: %v", domain, err)
	}

	// Get host information if no host is specified.
	if hostName == "" {
		var err error
		hostName, err = os.Hostname()
		if err != nil {
			return nil, fmt.Errorf("could not determine host: %v", err)
		}
		hostName = fmt.Sprintf("%s.", hostName)
	}
	if err := validateFQDN(hostName); err != nil {
		return nil, fmt.Errorf("hostName %q is not a fully-qualified domain name: %v", hostName, err)
	}

	if len(ips) == 0 {
		var err error
		ips, err = net.LookupIP(hostName)
		if err != nil {
			// Try appending the host domain suffix and lookup again
			// (required for Linux-based hosts)
			tmpHostName := fmt.Sprintf("%s%s", hostName, domain)

			ips, err = net.LookupIP(tmpHostName)

			if err != nil {
				return nil, fmt.Errorf("could not determine host IP addresses for %s", hostName)
			}
		}
	}
	for _, ip := range ips {
		if ip.To4() == nil && ip.To16() == nil {
			return nil, fmt.Errorf("invalid IP address in IPs list: %v", ip)
		}
	}

	return &MDNSService{
		Instance:     instance,
		Service:      service,
		Domain:       domain,
		HostName:     hostName,
		Port:         port,
		IPs:          ips,
		TXT:          txt,
		serviceAddr:  fmt.Sprintf("%s.%s.", trimDot(service), trimDot(domain)),
		instanceAddr: fmt.Sprintf("%s.%s.%s.", instance, trimDot(service), trimDot(domain)),
		enumAddr:     fmt.Sprintf("_services._dns-sd._udp.%s.", trimDot(domain)),
	}, nil
}

// trimDot is used to trim the dots from the start or end of a string
func trimDot(s string) string {
	return strings.Trim(s, ".")
}

// Records returns DNS records in response to a DNS question.
func (m *MDNSService) Records(q dns.Question) []dns.RR {
	switch q.Name {
	case m.enumAddr:
		return m.serviceEnum(q)
	case m.serviceAddr:
		return m.serviceRecords(q)
	case m.instanceAddr:
		return m.instanceRecords(q)
	case m.HostName:
		if q.Qtype == dns.TypeA || q.Qtype == dns.TypeAAAA {
			return m.instanceRecords(q)
		}
		fallthrough
	default:
		return nil
	}
}

func (m *MDNSService) serviceEnum(q dns.Question) []dns.RR {
	switch q.Qtype {
	case dns.TypeANY:
		fallthrough
	case dns.TypePTR:
		rr := &dns.PTR{
			Hdr: dns.RR_Header{
				Name:   q.Name,
				Rrtype: dns.TypePTR,
				Class:  dns.ClassINET,
				Ttl:    defaultTTL,
			},
			Ptr: m.serviceAddr,
		}
		return []dns.RR{rr}
	default:
		return nil
	}
}

// serviceRecords is called when the query matches the service name
func (m *MDNSService) serviceRecords(q dns.Question) []dns.RR {
	switch q.Qtype {
	case dns.TypeANY:
		fallthrough
	case dns.TypePTR:
		// Build a PTR response for the service
		rr := &dns.PTR{
			Hdr: dns.RR_Header{
				Name:   q.Name,
				Rrtype: dns.TypePTR,
				Class:  dns.ClassINET,
				Ttl:    defaultTTL,
			},
			Ptr: m.instanceAddr,
		}
		servRec := []dns.RR{rr}

		// Get the instance records
		instRecs := m.instanceRecords(dns.Question{
			Name:  m.instanceAddr,
			Qtype: dns.TypeANY,
		})

		// Return the service record with the instance records
		return append(servRec, instRecs...)
	default:
		return nil
	}
}

// serviceRecords is called when the query matches the instance name
func (m *MDNSService) instanceRecords(q dns.Question) []dns.RR {
	switch q.Qtype {
	case dns.TypeANY:
		// Get the SRV, which includes A and AAAA
		recs := m.instanceRecords(dns.Question{
			Name:  m.instanceAddr,
			Qtype: dns.TypeSRV,
		})

		// Add the TXT record
		recs = append(recs, m.instanceRecords(dns.Question{
			Name:  m.instanceAddr,
			Qtype: dns.TypeTXT,
		})...)
		return recs

	case dns.TypeA:
		var rr []dns.RR
		for _, ip := range m.IPs {
			if ip4 := ip.To4(); ip4 != nil {
				rr = append(rr, &dns.A{
					Hdr: dns.RR_Header{
						Name:   m.HostName,
						Rrtype: dns.TypeA,
						Class:  dns.ClassINET,
						Ttl:    defaultTTL,
					},
					A: ip4,
				})
			}
		}
		return rr

	case dns.TypeAAAA:
		var rr []dns.RR
		for _, ip := range m.IPs {
			if ip.To4() != nil {
				// TODO(reddaly): IPv4 addresses could be encoded in IPv6 format and
				// putinto AAAA records, but the current logic puts ipv4-encodable
				// addresses into the A records exclusively.  Perhaps this should be
				// configurable?
				continue
			}

			if ip16 := ip.To16(); ip16 != nil {
				rr = append(rr, &dns.AAAA{
					Hdr: dns.RR_Header{
						Name:   m.HostName,
						Rrtype: dns.TypeAAAA,
						Class:  dns.ClassINET,
						Ttl:    defaultTTL,
					},
					AAAA: ip16,
				})
			}
		}
		return rr

	case dns.TypeSRV:
		// Create the SRV Record
		srv := &dns.SRV{
			Hdr: dns.RR_Header{
				Name:   q.Name,
				Rrtype: dns.TypeSRV,
				Class:  dns.ClassINET,
				Ttl:    defaultTTL,
			},
			Priority: 10,
			Weight:   1,
			Port:     uint16(m.Port),
			Target:   m.HostName,
		}
		recs := []dns.RR{srv}

		// Add the A record
		recs = append(recs, m.instanceRecords(dns.Question{
			Name:  m.instanceAddr,
			Qtype: dns.TypeA,
		})...)

		// Add the AAAA record
		recs = append(recs, m.instanceRecords(dns.Question{
			Name:  m.instanceAddr,
			Qtype: dns.TypeAAAA,
		})...)
		return recs

	case dns.TypeTXT:
		txt := &dns.TXT{
			Hdr: dns.RR_Header{
				Name:   q.Name,
				Rrtype: dns.TypeTXT,
				Class:  dns.ClassINET,
				Ttl:    defaultTTL,
			},
			Txt: m.TXT,
		}
		return []dns.RR{txt}
	}
	return nil
}
<commit_msg>net.InterfaceAddrs() instead of net.LookupIP(myhost)<commit_after>package mdns

import (
	"fmt"
	"net"
	"os"
	"strings"

	"github.com/miekg/dns"
)

const (
	// defaultTTL is the default TTL value in returned DNS records in seconds.
	defaultTTL = 120
)

// Zone is the interface used to integrate with the server and
// to serve records dynamically
type Zone interface {
	// Records returns DNS records in response to a DNS question.
	Records(q dns.Question) []dns.RR
}

// MDNSService is used to export a named service by implementing a Zone
type MDNSService struct {
	Instance string   // Instance name (e.g. "hostService name")
	Service  string   // Service name (e.g. "_http._tcp.")
	Domain   string   // If blank, assumes "local"
	HostName string   // Host machine DNS name (e.g. "mymachine.net.")
	Port     int      // Service Port
	IPs      []net.IP // IP addresses for the service's host
	TXT      []string // Service TXT records

	serviceAddr  string // Fully qualified service address
	instanceAddr string // Fully qualified instance address
	enumAddr     string // _services._dns-sd._udp.<domain>
}

// validateFQDN returns an error if the passed string is not a fully qualified
// hdomain name (more specifically, a hostname).
func validateFQDN(s string) error {
	if len(s) == 0 {
		return fmt.Errorf("FQDN must not be blank")
	}
	if s[len(s)-1] != '.' {
		return fmt.Errorf("FQDN must end in period: %s", s)
	}
	// TODO(reddaly): Perform full validation.

	return nil
}

// NewMDNSService returns a new instance of MDNSService.
//
// If domain, hostName, or ips is set to the zero value, then a default value
// will be inferred from the operating system.
//
// TODO(reddaly): This interface may need to change to account for "unique
// record" conflict rules of the mDNS protocol.  Upon startup, the server should
// check to ensure that the instance name does not conflict with other instance
// names, and, if required, select a new name.  There may also be conflicting
// hostName A/AAAA records.
func NewMDNSService(instance, service, domain, hostName string, port int, ips []net.IP, txt []string) (*MDNSService, error) {
	// Sanity check inputs
	if instance == "" {
		return nil, fmt.Errorf("missing service instance name")
	}
	if service == "" {
		return nil, fmt.Errorf("missing service name")
	}
	if port == 0 {
		return nil, fmt.Errorf("missing service port")
	}

	// Set default domain
	if domain == "" {
		domain = "local."
	}
	if err := validateFQDN(domain); err != nil {
		return nil, fmt.Errorf("domain %q is not a fully-qualified domain name: %v", domain, err)
	}

	// Get host information if no host is specified.
	if hostName == "" {
		var err error
		hostName, err = os.Hostname()
		if err != nil {
			return nil, fmt.Errorf("could not determine host: %v", err)
		}
		hostName = fmt.Sprintf("%s.", hostName)
	}
	if err := validateFQDN(hostName); err != nil {
		return nil, fmt.Errorf("hostName %q is not a fully-qualified domain name: %v", hostName, err)
	}

	if len(ips) == 0 {
		addrs, err := net.InterfaceAddrs()
		if err != nil {
			return nil, fmt.Errorf("could not determine interface IP addresses")
		}
		for _, addr := range addrs {
			var ip net.IP
			switch v := addr.(type) {
			case *net.IPNet:
				ip = v.IP
			case *net.IPAddr:
				ip = v.IP
			}
			ips = append(ips, ip)
		}
	}
	for _, ip := range ips {
		if ip.To4() == nil && ip.To16() == nil {
			return nil, fmt.Errorf("invalid IP address in IPs list: %v", ip)
		}
	}

	return &MDNSService{
		Instance:     instance,
		Service:      service,
		Domain:       domain,
		HostName:     hostName,
		Port:         port,
		IPs:          ips,
		TXT:          txt,
		serviceAddr:  fmt.Sprintf("%s.%s.", trimDot(service), trimDot(domain)),
		instanceAddr: fmt.Sprintf("%s.%s.%s.", instance, trimDot(service), trimDot(domain)),
		enumAddr:     fmt.Sprintf("_services._dns-sd._udp.%s.", trimDot(domain)),
	}, nil
}

// trimDot is used to trim the dots from the start or end of a string
func trimDot(s string) string {
	return strings.Trim(s, ".")
}

// Records returns DNS records in response to a DNS question.
func (m *MDNSService) Records(q dns.Question) []dns.RR {
	switch q.Name {
	case m.enumAddr:
		return m.serviceEnum(q)
	case m.serviceAddr:
		return m.serviceRecords(q)
	case m.instanceAddr:
		return m.instanceRecords(q)
	case m.HostName:
		if q.Qtype == dns.TypeA || q.Qtype == dns.TypeAAAA {
			return m.instanceRecords(q)
		}
		fallthrough
	default:
		return nil
	}
}

func (m *MDNSService) serviceEnum(q dns.Question) []dns.RR {
	switch q.Qtype {
	case dns.TypeANY:
		fallthrough
	case dns.TypePTR:
		rr := &dns.PTR{
			Hdr: dns.RR_Header{
				Name:   q.Name,
				Rrtype: dns.TypePTR,
				Class:  dns.ClassINET,
				Ttl:    defaultTTL,
			},
			Ptr: m.serviceAddr,
		}
		return []dns.RR{rr}
	default:
		return nil
	}
}

// serviceRecords is called when the query matches the service name
func (m *MDNSService) serviceRecords(q dns.Question) []dns.RR {
	switch q.Qtype {
	case dns.TypeANY:
		fallthrough
	case dns.TypePTR:
		// Build a PTR response for the service
		rr := &dns.PTR{
			Hdr: dns.RR_Header{
				Name:   q.Name,
				Rrtype: dns.TypePTR,
				Class:  dns.ClassINET,
				Ttl:    defaultTTL,
			},
			Ptr: m.instanceAddr,
		}
		servRec := []dns.RR{rr}

		// Get the instance records
		instRecs := m.instanceRecords(dns.Question{
			Name:  m.instanceAddr,
			Qtype: dns.TypeANY,
		})

		// Return the service record with the instance records
		return append(servRec, instRecs...)
	default:
		return nil
	}
}

// serviceRecords is called when the query matches the instance name
func (m *MDNSService) instanceRecords(q dns.Question) []dns.RR {
	switch q.Qtype {
	case dns.TypeANY:
		// Get the SRV, which includes A and AAAA
		recs := m.instanceRecords(dns.Question{
			Name:  m.instanceAddr,
			Qtype: dns.TypeSRV,
		})

		// Add the TXT record
		recs = append(recs, m.instanceRecords(dns.Question{
			Name:  m.instanceAddr,
			Qtype: dns.TypeTXT,
		})...)
		return recs

	case dns.TypeA:
		var rr []dns.RR
		for _, ip := range m.IPs {
			if ip4 := ip.To4(); ip4 != nil {
				rr = append(rr, &dns.A{
					Hdr: dns.RR_Header{
						Name:   m.HostName,
						Rrtype: dns.TypeA,
						Class:  dns.ClassINET,
						Ttl:    defaultTTL,
					},
					A: ip4,
				})
			}
		}
		return rr

	case dns.TypeAAAA:
		var rr []dns.RR
		for _, ip := range m.IPs {
			if ip.To4() != nil {
				// TODO(reddaly): IPv4 addresses could be encoded in IPv6 format and
				// putinto AAAA records, but the current logic puts ipv4-encodable
				// addresses into the A records exclusively.  Perhaps this should be
				// configurable?
				continue
			}

			if ip16 := ip.To16(); ip16 != nil {
				rr = append(rr, &dns.AAAA{
					Hdr: dns.RR_Header{
						Name:   m.HostName,
						Rrtype: dns.TypeAAAA,
						Class:  dns.ClassINET,
						Ttl:    defaultTTL,
					},
					AAAA: ip16,
				})
			}
		}
		return rr

	case dns.TypeSRV:
		// Create the SRV Record
		srv := &dns.SRV{
			Hdr: dns.RR_Header{
				Name:   q.Name,
				Rrtype: dns.TypeSRV,
				Class:  dns.ClassINET,
				Ttl:    defaultTTL,
			},
			Priority: 10,
			Weight:   1,
			Port:     uint16(m.Port),
			Target:   m.HostName,
		}
		recs := []dns.RR{srv}

		// Add the A record
		recs = append(recs, m.instanceRecords(dns.Question{
			Name:  m.instanceAddr,
			Qtype: dns.TypeA,
		})...)

		// Add the AAAA record
		recs = append(recs, m.instanceRecords(dns.Question{
			Name:  m.instanceAddr,
			Qtype: dns.TypeAAAA,
		})...)
		return recs

	case dns.TypeTXT:
		txt := &dns.TXT{
			Hdr: dns.RR_Header{
				Name:   q.Name,
				Rrtype: dns.TypeTXT,
				Class:  dns.ClassINET,
				Ttl:    defaultTTL,
			},
			Txt: m.TXT,
		}
		return []dns.RR{txt}
	}
	return nil
}
<|endoftext|>


<commit_before>package zoom

import (
	"errors"
	"fmt"
	"github.com/stephenalexbrowne/zoom/redis"
	"reflect"
)

// File contains glue code that connects the Model
// abstraction to the database. In other words,
// this is where the magic happens. The most important
// public-facing methods are here.

// writes the interface to the redis database
// throws an error if the type has not yet been
// registered. If in.Id is nil, will mutate in
// by setting the Id.
func Save(in ModelInterface) error {

	// make sure we'll dealing with a pointer
	typ := reflect.TypeOf(in)
	if typ.Kind() != reflect.Ptr {
		msg := fmt.Sprintf("zoom: Save() requires a pointer as an argument. The type %T is not a pointer.", in)
		return errors.New(msg)
	}

	// get the value
	val := reflect.ValueOf(in)
	if val.IsNil() {
		return errors.New("zoom: attempted to call save on a nil pointer!")
	}

	// get a connection from the pool
	conn := pool.Get()
	defer conn.Close()

	// get the struct spec
	ss := structSpecForType(typ.Elem())

	// get the registered name
	name, err := getRegisteredNameFromInterface(in)
	if err != nil {
		return err
	}

	// prepare the arguments for redis driver
	// if no id was provided, we should generate one
	if in.GetId() == "" {
		in.SetId(generateRandomId())
	}
	key := name + ":" + in.GetId()

	// start a multi/exec command. i.e. create a command queue
	if err := conn.Send("multi"); err != nil {
		// cancel transaction and return err
		conn.Do("discard")
		return err
	}

	// add command to queue
	if err := conn.Send("hmset", Args{}.Add(key).AddFlat(in)...); err != nil {
		// cancel transaction and return err
		conn.Do("discard")
		return err
	}

	// add to the index for this model
	err = queueAddToIndex(name, in.GetId(), conn)
	if err != nil {
		return err
	}

	// save the relations
	err = saveRelations(in, val, ss, name, conn)
	if err != nil {
		return err
	}

	// finally, commit the transaction
	// they were all writes, so the return value isn't needed
	if _, err := conn.Do("exec"); err != nil {
		return err
	}

	// add to the cache
	modelCache.Set(key, newCacheValue(in))

	return nil
}

// Removes the record from the database
func Delete(in ModelInterface) error {

	// get the registered name
	name, err := getRegisteredNameFromInterface(in)
	if err != nil {
		return err
	}

	// TODO: make sure it has an id

	return DeleteById(name, in.GetId())
}

// Delete a record from the interface by its id only
func DeleteById(modelName, id string) error {

	key := modelName + ":" + id

	// get a connection
	conn := pool.Get()
	defer conn.Close()

	// start a transaction
	conn.Send("multi")

	// add a command to the queue which will
	// delete the main key
	if err := conn.Send("del", key); err != nil {
		return err
	}

	// add a command to the queue which will
	// remove it from the index
	key = modelName + ":index"
	if err := conn.Send("srem", key, id); err != nil {
		return err
	}

	// execute the commands
	_, err := conn.Do("exec")
	if err != nil {
		return err
	}

	// remove from the cache
	modelCache.Delete(key)

	return nil
}

// Find a model by modelName and id. modelName must be the
// same name that was used in the Register() call
func FindById(modelName, id string) (interface{}, error) {
	// get the registered type
	typ, err := getRegisteredTypeFromName(modelName)
	if err != nil {
		return nil, err
	}

	// create the key based on the modelName and id
	key := modelName + ":" + id

	// check if the model is in the cache
	val, found := modelCache.Get(key)
	if found {
		cv, ok := val.(*cacheValue)
		if !ok {
			return nil, errors.New("zoom: Got from cache but couldn't convert to cacheValue")
		}
		return cv.value, nil
	}

	// open a connection
	conn := pool.Get()
	defer conn.Close()

	// make sure the key exists
	exists, err := KeyExists(key, conn)
	if err != nil {
		return nil, err
	}
	if !exists {
		msg := fmt.Sprintf("Couldn't find %s with id = %s", modelName, id)
		return nil, NewKeyNotFoundError(msg)
	}

	// create a new struct and instantiate its Model attribute
	// this gives us the embedded methods and properties on Model
	modelVal := reflect.New(typ.Elem())
	modelVal.Elem().FieldByName("Model").Set(reflect.ValueOf(new(Model)))

	// type assert to ModelInterface so we can use SetId()
	model := modelVal.Interface().(ModelInterface)

	// get the field values from redis
	reply, err := conn.Do("hgetall", key)
	bulk, err := redis.MultiBulk(reply, err)
	if err != nil {
		return nil, err
	}

	// fill in the values of the struct
	err = ScanStruct(bulk, model)
	if err != nil {
		return nil, err
	}

	// set the id
	model.SetId(id)

	// scan relations and add them as attributes to model
	ss := structSpecForType(typ.Elem())
	if err := scanRelations(ss, modelName, id, modelVal, conn); err != nil {
		return nil, err
	}

	// return it
	return model, nil
}

// ScanById is like FindById, but it will scan the results from the database
// into model, avoiding the need for typecasting after the find.
func ScanById(model ModelInterface, id string) error {

	// get the type and name
	typ := reflect.TypeOf(model)
	modelName, found := typeToName[typ]
	if !found {
		return NewModelTypeNotRegisteredError(typ)
	}

	// create the key based on the modelName and id
	key := modelName + ":" + id

	// check if the model is in the cache
	val, found := modelCache.Get(key)
	if found {
		cv, ok := val.(*cacheValue)
		if !ok {
			return errors.New("zoom: Got from cache but couldn't convert to cacheValue")
		}
		modelVal := reflect.ValueOf(model).Elem()
		modelVal.Set(reflect.ValueOf(cv.value).Elem())
		return nil
	}

	// open a connection
	conn := pool.Get()
	defer conn.Close()

	// make sure the key exists
	exists, err := KeyExists(key, conn)
	if err != nil {
		return err
	}
	if !exists {
		msg := fmt.Sprintf("Couldn't find %s with id = %s", modelName, id)
		return NewKeyNotFoundError(msg)
	}

	// get the stuff from redis
	reply, err := conn.Do("hgetall", key)
	bulk, err := redis.MultiBulk(reply, err)
	if err != nil {
		return err
	}

	// create a new struct and instantiate its Model attribute
	// this gives us the embedded methods and properties on Model
	modelVal := reflect.ValueOf(model)
	modelVal.Elem().FieldByName("Model").Set(reflect.ValueOf(new(Model)))

	// invoke redis driver to fill in the values of the struct
	err = ScanStruct(bulk, model)
	if err != nil {
		return err
	}

	// set the id
	model.SetId(id)

	// scan relations and add them as attributes to model
	ss := structSpecForType(typ.Elem())
	if err := scanRelations(ss, modelName, id, modelVal, conn); err != nil {
		return err
	}

	return nil
}

func FindAll(modelName string) ([]interface{}, error) {

	// get a connection
	conn := pool.Get()
	defer conn.Close()

	// invoke redis driver to get indexed keys and convert to []interface{}
	key := modelName + ":index"
	ids, err := redis.Strings(conn.Do("smembers", key))
	if err != nil {
		return nil, err
	}

	// iterate through each id. find the corresponding model. append to results.
	results := make([]interface{}, len(ids), len(ids))
	for i, id := range ids {
		m, err := FindById(modelName, id)
		if err != nil {
			return nil, err
		}
		results[i] = m
	}

	return results, nil
}
<commit_msg>Add to cache on Find operations. (Leaving it out would mean technically not following LRU)<commit_after>package zoom

import (
	"errors"
	"fmt"
	"github.com/stephenalexbrowne/zoom/redis"
	"reflect"
)

// File contains glue code that connects the Model
// abstraction to the database. In other words,
// this is where the magic happens. The most important
// public-facing methods are here.

// writes the interface to the redis database
// throws an error if the type has not yet been
// registered. If in.Id is nil, will mutate in
// by setting the Id.
func Save(in ModelInterface) error {

	// make sure we'll dealing with a pointer
	typ := reflect.TypeOf(in)
	if typ.Kind() != reflect.Ptr {
		msg := fmt.Sprintf("zoom: Save() requires a pointer as an argument. The type %T is not a pointer.", in)
		return errors.New(msg)
	}

	// get the value
	val := reflect.ValueOf(in)
	if val.IsNil() {
		return errors.New("zoom: attempted to call save on a nil pointer!")
	}

	// get a connection from the pool
	conn := pool.Get()
	defer conn.Close()

	// get the struct spec
	ss := structSpecForType(typ.Elem())

	// get the registered name
	name, err := getRegisteredNameFromInterface(in)
	if err != nil {
		return err
	}

	// prepare the arguments for redis driver
	// if no id was provided, we should generate one
	if in.GetId() == "" {
		in.SetId(generateRandomId())
	}
	key := name + ":" + in.GetId()

	// start a multi/exec command. i.e. create a command queue
	if err := conn.Send("multi"); err != nil {
		// cancel transaction and return err
		conn.Do("discard")
		return err
	}

	// add command to queue
	if err := conn.Send("hmset", Args{}.Add(key).AddFlat(in)...); err != nil {
		// cancel transaction and return err
		conn.Do("discard")
		return err
	}

	// add to the index for this model
	err = queueAddToIndex(name, in.GetId(), conn)
	if err != nil {
		return err
	}

	// save the relations
	err = saveRelations(in, val, ss, name, conn)
	if err != nil {
		return err
	}

	// finally, commit the transaction
	// they were all writes, so the return value isn't needed
	if _, err := conn.Do("exec"); err != nil {
		return err
	}

	// add to the cache
	modelCache.Set(key, newCacheValue(in))

	return nil
}

// Removes the record from the database
func Delete(in ModelInterface) error {

	// get the registered name
	name, err := getRegisteredNameFromInterface(in)
	if err != nil {
		return err
	}

	// TODO: make sure it has an id

	return DeleteById(name, in.GetId())
}

// Delete a record from the interface by its id only
func DeleteById(modelName, id string) error {

	key := modelName + ":" + id

	// get a connection
	conn := pool.Get()
	defer conn.Close()

	// start a transaction
	conn.Send("multi")

	// add a command to the queue which will
	// delete the main key
	if err := conn.Send("del", key); err != nil {
		return err
	}

	// add a command to the queue which will
	// remove it from the index
	key = modelName + ":index"
	if err := conn.Send("srem", key, id); err != nil {
		return err
	}

	// execute the commands
	_, err := conn.Do("exec")
	if err != nil {
		return err
	}

	// remove from the cache
	modelCache.Delete(key)

	return nil
}

// Find a model by modelName and id. modelName must be the
// same name that was used in the Register() call
func FindById(modelName, id string) (interface{}, error) {
	// get the registered type
	typ, err := getRegisteredTypeFromName(modelName)
	if err != nil {
		return nil, err
	}

	// create the key based on the modelName and id
	key := modelName + ":" + id

	// check if the model is in the cache
	val, found := modelCache.Get(key)
	if found {
		cv, ok := val.(*cacheValue)
		if !ok {
			return nil, errors.New("zoom: Got from cache but couldn't convert to cacheValue")
		}
		return cv.value, nil
	}

	// open a connection
	conn := pool.Get()
	defer conn.Close()

	// make sure the key exists
	exists, err := KeyExists(key, conn)
	if err != nil {
		return nil, err
	}
	if !exists {
		msg := fmt.Sprintf("Couldn't find %s with id = %s", modelName, id)
		return nil, NewKeyNotFoundError(msg)
	}

	// create a new struct and instantiate its Model attribute
	// this gives us the embedded methods and properties on Model
	modelVal := reflect.New(typ.Elem())
	modelVal.Elem().FieldByName("Model").Set(reflect.ValueOf(new(Model)))

	// type assert to ModelInterface so we can use SetId()
	model := modelVal.Interface().(ModelInterface)

	// get the field values from redis
	reply, err := conn.Do("hgetall", key)
	bulk, err := redis.MultiBulk(reply, err)
	if err != nil {
		return nil, err
	}

	// fill in the values of the struct
	err = ScanStruct(bulk, model)
	if err != nil {
		return nil, err
	}

	// set the id
	model.SetId(id)

	// scan relations and add them as attributes to model
	ss := structSpecForType(typ.Elem())
	if err := scanRelations(ss, modelName, id, modelVal, conn); err != nil {
		return nil, err
	}

	// add to the cache
	modelCache.SetIfAbsent(key, newCacheValue(model))

	// return it
	return model, nil
}

// ScanById is like FindById, but it will scan the results from the database
// into model, avoiding the need for typecasting after the find.
func ScanById(model ModelInterface, id string) error {

	// get the type and name
	typ := reflect.TypeOf(model)
	modelName, found := typeToName[typ]
	if !found {
		return NewModelTypeNotRegisteredError(typ)
	}

	// create the key based on the modelName and id
	key := modelName + ":" + id

	// check if the model is in the cache
	val, found := modelCache.Get(key)
	if found {
		cv, ok := val.(*cacheValue)
		if !ok {
			return errors.New("zoom: Got from cache but couldn't convert to cacheValue")
		}
		modelVal := reflect.ValueOf(model).Elem()
		modelVal.Set(reflect.ValueOf(cv.value).Elem())
		return nil
	}

	// open a connection
	conn := pool.Get()
	defer conn.Close()

	// make sure the key exists
	exists, err := KeyExists(key, conn)
	if err != nil {
		return err
	}
	if !exists {
		msg := fmt.Sprintf("Couldn't find %s with id = %s", modelName, id)
		return NewKeyNotFoundError(msg)
	}

	// get the stuff from redis
	reply, err := conn.Do("hgetall", key)
	bulk, err := redis.MultiBulk(reply, err)
	if err != nil {
		return err
	}

	// create a new struct and instantiate its Model attribute
	// this gives us the embedded methods and properties on Model
	modelVal := reflect.ValueOf(model)
	modelVal.Elem().FieldByName("Model").Set(reflect.ValueOf(new(Model)))

	// invoke redis driver to fill in the values of the struct
	err = ScanStruct(bulk, model)
	if err != nil {
		return err
	}

	// set the id
	model.SetId(id)

	// scan relations and add them as attributes to model
	ss := structSpecForType(typ.Elem())
	if err := scanRelations(ss, modelName, id, modelVal, conn); err != nil {
		return err
	}

	// add to the cache
	modelCache.SetIfAbsent(key, newCacheValue(model))

	return nil
}

func FindAll(modelName string) ([]interface{}, error) {

	// get a connection
	conn := pool.Get()
	defer conn.Close()

	// invoke redis driver to get indexed keys and convert to []interface{}
	key := modelName + ":index"
	ids, err := redis.Strings(conn.Do("smembers", key))
	if err != nil {
		return nil, err
	}

	// iterate through each id. find the corresponding model. append to results.
	results := make([]interface{}, len(ids), len(ids))
	for i, id := range ids {
		m, err := FindById(modelName, id)
		if err != nil {
			return nil, err
		}
		results[i] = m
	}

	return results, nil
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/rand"
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"strings"
	"sync"
	"syscall"
	"time"
)

// How long of a timeout should indicate the end of input.
const timeout = 10 * time.Millisecond

type Zork struct {
	Cmd        *exec.Cmd   // The zork process.
	Exit       chan error  // Channel with zork's exit status.
	Error      chan error  // Channel for our errors.
	ZorkInput  io.Writer   // Zork's stdin.
	ZorkOutput chan string // Stream of output from
	Stdin      chan string // Stream of input from stdin.
	Mutex      sync.Mutex  // Mutex to protect
}

func RunZork(dfrotz, zork1Dat string) (err error) {
	z := new(Zork)
	z.Error = make(chan error, 2)
	z.Cmd = exec.Command(dfrotz, zork1Dat)
	// Pipes to process.
	z.ZorkInput, err = z.Cmd.StdinPipe()
	if err != nil {
		return
	}
	var stdout io.ReadCloser
	stdout, err = z.Cmd.StdoutPipe()
	if err != nil {
		return
	}
	// Start process.
	if err := z.Cmd.Start(); err != nil {
		log.Fatal(err)
	}
	// Wait for process to exit.
	z.Exit = make(chan error, 1)
	go func() {
		z.Exit <- z.Cmd.Wait()
	}()
	// Kill the process when finished if it isn't already dead
	defer func() {
		// Signal 0 checks if the process is alive.
		if z.Cmd.Process.Signal(syscall.Signal(0)) == nil {
			z.Cmd.Process.Kill()
		}
	}()
	// Channels to read data from zork's stdout and our stdin.
	z.ZorkOutput = sepByTimeout(stdout, timeout)
	z.Stdin = sepByTimeout(os.Stdin, timeout)

	// Handle initial output.
	z.HandleAsync("", <-z.ZorkOutput)
	// Main loop.
	for {
		// Read input from stdin (excepting errors).
		select {
		case input := <-z.Stdin:
			// Pipe the input to zork and read the output.
			input = strings.TrimSpace(input)
			output, err := z.EvaluateCommand(input)
			if err != nil {
				return err
			}
			// Process the input/output pair.
			go z.Handle(input, output)
		case err := <-z.Exit:
			return err
		case err := <-z.Error:
			return err
		}
	}
}

// Returns a channel that yields all data received by a reader only after
// a timeout is reached. Needed for zork because there is never an EOF, and you
// ordinarly only know the end of input by visually seeing that no more input
// is coming.
func sepByTimeout(r io.Reader, timeout time.Duration) chan string {
	// Continuosly read chunks.
	chunks := make(chan []byte)
	go func() {
		chunk := make([]byte, 4096)
		for {
			n, err := r.Read(chunk)
			// End input on error or if nothing is read.
			if n == 0 || err != nil {
				close(chunks)
				return
			}
			chunks <- chunk[:n]
		}
	}()
	// Accumulate chunks until the timeout is reached.
	result := make(chan string)
	var buf []byte
	go func() {
		for {
			select {
			// Another chunk, timeout not reached yet.
			case chunk, ok := <-chunks:
				// End of input.
				if !ok {
					close(result)
					return
				}
				buf = append(buf, chunk...)
			// Timeout occurs before next data, yield accumulated buffer.
			case <-time.After(timeout):
				if len(buf) > 0 {
					result <- string(buf)
					buf = nil
				}
			}
		}
	}()
	return result
}

// Input a command (excluding newline) into zork and return its output.
func (z *Zork) EvaluateCommand(command string) (output string, err error) {
	// Write the input (plus a newline) to zork.
	if _, err = z.ZorkInput.Write([]byte(command + "\n")); err != nil {
		return
	}
	// Read the output from zork, excepting the process dying.
	select {
	case output = <-z.ZorkOutput:
		return
	case err = <-z.Exit:
		return
	case err = <-z.Error:
		return
	}
}

func (z *Zork) HandleAsync(input, output string) {
	if err := z.Handle(input, output); err != nil {
		z.Error <- err
	}
}

func (z *Zork) Handle(input, output string) (err error) {
	// Only let this run once at a time.
	z.Mutex.Lock()
	defer z.Mutex.Unlock()
	// Print the output like it usually would.
	fmt.Print(output)
	os.Stdout.Sync()

	// Save to a random file, as demo of what can be done!
	b := make([]byte, 8)
	rand.Read(b)
	name := hex.EncodeToString(b)
	if err = z.Save(name+".sav", false); err != nil {
		return
	}

	/*lines := strings.Split(output, "\n")
	// Determine if there is a header or not If there is not a header,
	// than a move was not completed.
	headerFields := strings.Fields(lines[0])
	n := len(headerFields)
	if n < 5 || headerFields[n-4] != "Score:" || headerFields[n-2] != "Moves:" {
		return
	}
	locationName := strings.Join(headerFields[:n-4], " ")
	// Strip the header, startup info, prompt.
	lines = lines[2:]
	if lines[0] == "ZORK I: The Great Underground Empire" {
		lines = lines[5:]
	}
	lines = lines[:len(lines)-2]
	if lines[0] == locationName {
		// lines now contains the description
	}*/
	return
}

func (z *Zork) Save(path string, overwrite bool) (err error) {
	var output string
	_, err = z.EvaluateCommand("save")
	if err != nil {
		return nil
	}
	output, err = z.EvaluateCommand(path)
	if err != nil {
		return
	}
	if output == "Overwrite existing file? " {
		if !overwrite {
			return errors.New("save file already exists")
		}
		output, _ = z.EvaluateCommand("y")
		if err != nil {
			return
		}
	}
	if !strings.HasPrefix(output, "Ok") {
		err = errors.New(output)
	}
	return
}

func main() {
	if len(os.Args) != 3 {
		log.Fatalf("usage: %s <dfrotz> <ZORK1.DAT>\n", os.Args[0])
	}
	if err := RunZork(os.Args[1], os.Args[2]); err != nil {
		log.Fatal(err)
	}
}
<commit_msg>commented out autosaving, made "quit" work, and combined Exit and Error<commit_after>package main

import (
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"strings"
	"sync"
	"syscall"
	"time"
)

// How long of a timeout should indicate the end of input.
const timeout = 10 * time.Millisecond

type Zork struct {
	Cmd        *exec.Cmd   // The zork process.
	Error      chan error  // Channel for errors or exit status.
	ZorkInput  io.Writer   // Zork's stdin.
	ZorkOutput chan string // Stream of output from
	Stdin      chan string // Stream of input from stdin.
	Mutex      sync.Mutex  // Mutex to protect
}

func RunZork(dfrotz, zork1Dat string) (err error) {
	z := new(Zork)
	z.Error = make(chan error, 2)
	z.Cmd = exec.Command(dfrotz, zork1Dat)
	// Pipes to process.
	z.ZorkInput, err = z.Cmd.StdinPipe()
	if err != nil {
		return
	}
	var stdout io.ReadCloser
	stdout, err = z.Cmd.StdoutPipe()
	if err != nil {
		return
	}
	// Start process.
	if err := z.Cmd.Start(); err != nil {
		log.Fatal(err)
	}
	// Wait for process to exit.
	go func() {
		z.Error <- z.Cmd.Wait()
	}()
	// Kill the process when finished if it isn't already dead
	defer func() {
		// Signal 0 checks if the process is alive.
		if z.Cmd.Process.Signal(syscall.Signal(0)) == nil {
			z.Cmd.Process.Kill()
		}
	}()
	// Channels to read data from zork's stdout and our stdin.
	z.ZorkOutput = sepByTimeout(stdout, timeout)
	z.Stdin = sepByTimeout(os.Stdin, timeout)

	// Handle initial output.
	z.HandleAsync("", <-z.ZorkOutput)
	// Main loop.
	for {
		// Read input from stdin (excepting errors).
		select {
		case input := <-z.Stdin:
			// Pipe the input to zork and read the output.
			input = strings.TrimSpace(input)
			output, err := z.EvaluateCommand(input)
			if err != nil {
				return err
			}
			// Process the input/output pair.
			go z.Handle(input, output)
		case err := <-z.Error:
			return err
		}
	}
}

// Returns a channel that yields all data received by a reader only after
// a timeout is reached. Needed for zork because there is never an EOF, and you
// ordinarly only know the end of input by visually seeing that no more input
// is coming.
func sepByTimeout(r io.ReadCloser, timeout time.Duration) chan string {
	// Continuosly read chunks.
	chunks := make(chan []byte)
	go func() {
		chunk := make([]byte, 4096)
		for {
			n, err := r.Read(chunk)
			// End input on error or if nothing is read.
			if n == 0 || err != nil {
				r.Close()
				close(chunks)
				return
			}
			chunks <- chunk[:n]
		}
	}()
	// Accumulate chunks until the timeout is reached.
	result := make(chan string)
	var buf []byte
	go func() {
		for {
			select {
			// Another chunk, timeout not reached yet.
			case chunk, ok := <-chunks:
				// End of input.
				if !ok {
					close(result)
					return
				}
				buf = append(buf, chunk...)
			// Timeout occurs before next data, yield accumulated buffer.
			case <-time.After(timeout):
				if len(buf) > 0 {
					result <- string(buf)
					buf = nil
				}
			}
		}
	}()
	return result
}

// Input a command (excluding newline) into zork and return its output.
func (z *Zork) EvaluateCommand(command string) (output string, err error) {
	// Write the input (plus a newline) to zork.
	if _, err = z.ZorkInput.Write([]byte(command + "\n")); err != nil {
		return
	}
	// Read the output from zork, excepting the process dying.
	select {
	case output = <-z.ZorkOutput:
		return
	case err = <-z.Error:
		return
	}
}

func (z *Zork) HandleAsync(input, output string) {
	if err := z.Handle(input, output); err != nil {
		z.Error <- err
	}
}

func (z *Zork) Handle(input, output string) (err error) {
	// Only let this run once at a time.
	z.Mutex.Lock()
	defer z.Mutex.Unlock()
	// Print the output like it usually would.
	fmt.Print(output)
	os.Stdout.Sync()

	// Save to a random file, as demo of what can be done!
	//b := make([]byte, 8)
	//rand.Read(b)
	//name := hex.EncodeToString(b)
	//if err = z.Save(name+".sav", false); err != nil {
	//	return
	//}

	/*lines := strings.Split(output, "\n")
	// Determine if there is a header or not If there is not a header,
	// than a move was not completed.
	headerFields := strings.Fields(lines[0])
	n := len(headerFields)
	if n < 5 || headerFields[n-4] != "Score:" || headerFields[n-2] != "Moves:" {
		return
	}
	locationName := strings.Join(headerFields[:n-4], " ")
	// Strip the header, startup info, prompt.
	lines = lines[2:]
	if lines[0] == "ZORK I: The Great Underground Empire" {
		lines = lines[5:]
	}
	lines = lines[:len(lines)-2]
	if lines[0] == locationName {
		// lines now contains the description
	}*/
	return
}

func (z *Zork) Save(path string, overwrite bool) (err error) {
	var output string
	_, err = z.EvaluateCommand("save")
	if err != nil {
		return nil
	}
	output, err = z.EvaluateCommand(path)
	if err != nil {
		return
	}
	if output == "Overwrite existing file? " {
		if !overwrite {
			return errors.New("save file already exists")
		}
		output, _ = z.EvaluateCommand("y")
		if err != nil {
			return
		}
	}
	if !strings.HasPrefix(output, "Ok") {
		err = errors.New(output)
	}
	return
}

func main() {
	if len(os.Args) != 3 {
		log.Fatalf("usage: %s <dfrotz> <ZORK1.DAT>\n", os.Args[0])
	}
	if err := RunZork(os.Args[1], os.Args[2]); err != nil {
		log.Fatal(err)
	}
}
<|endoftext|>


<commit_before>package main

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

type Scanner struct {
	x, y, units, xStep, yStep float64
	relative                  bool
}

var filter map[string]func(string) (byte, byte, bool)
var context Scanner

func setup() {
	filter = make(map[string]func(string) (byte, byte, bool))
	filter["X"] = func(str string) (byte, byte, bool) {
		tmp, error := strconv.ParseFloat(str, 64)
		if error != nil {
			panic("not a number")
		}
		tmp *= context.units * context.xStep
		if context.relative {
			context.x += tmp
		} else {
			context.x = tmp
		}
		return uint8(context.x / 256), uint8(context.x), true
	}
	filter["Y"] = func(str string) (byte, byte, bool) {
		tmp, error := strconv.ParseFloat(str, 64)
		if error != nil {
			panic("not a number")
		}
		tmp *= context.yStep
		if context.relative {
			context.y += tmp
		} else {
			context.y = tmp
		}
		return uint8(context.y / 256), uint8(context.y), true
	}
	filter["S"] = func(str string) (byte, byte, bool) {
		tmp, error := strconv.ParseInt(str, 10, 16)
		if error != nil {
			panic("not a number")
		}
		return uint8(tmp / 256), uint8(tmp), true
	}
	filter["M"] = filter["S"]

	context.xStep = 100
	context.yStep = 10
	context.units = 1
}
func main() {
	setup()

	fmt.Println(execute("G0 X1.4 Y1.2123")) //move to 1.4 1.2123
	fmt.Println(execute("G91"))             //set relative
	fmt.Println(execute("G0 X1.4"))         //move to 2.8 1.2123
}
func execute(line string) []byte {
	tokens := strings.Split(line, " ")
	switch tokens[0] {
	case "G0", "G1":
		return compile([]string{"Y", "X"}, 1, tokens, filter)
	case "G4":
		return compile([]string{"S", "M"}, 3, tokens, filter)
	case "G20":
		setScale(2.6)
	case "G21":
		setScale(1.0)
	case "G90":
		setRelative(false)
	case "G91":
		setRelative(true)
	default:
		panic("Invalid command")
	}
	return nil
}

func setScale(scale float64) {
	context.units = scale
}
func setRelative(relative bool) {
	context.relative = relative
}

func compile(flags []string, code byte, tokens []string, filter map[string]func(string) (byte, byte, bool)) []byte {
	args := Parse(tokens, flags)
	out := make([]byte, 2)

	for i, arg := range args {
		first, second, set := filter[i](arg)
		if set {
			out = append(out, first, second)
		}
		out[1] += 1 << uint(IndexOf(i, flags))
	}

	out[0] = byte(len(out) - 1)

	out[1] = out[1]<<4 + code

	return out
}
func Parse(tokens []string, flags []string) map[string]string {
	out := make(map[string]string)
	for _, token := range tokens {
		for _, flag := range flags {
			if strings.HasPrefix(token, flag) {
				out[flag] = strings.TrimPrefix(token, flag)
			}
		}
	}
	return out
}
func IndexOf(elem string, array []string) int {
	for i, el := range array {
		if el == elem {
			return i
		}
	}
	return -1
}
<commit_msg>implement m codes<commit_after>package main

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

type Scanner struct {
	x, y, units, xStep, yStep float64
	v, a                      float64
	relative, tubeOn          bool
}

var filter map[string]func(string) (byte, byte, bool)
var context Scanner

func setup() {
	filter = make(map[string]func(string) (byte, byte, bool))
	filter["X"] = func(str string) (byte, byte, bool) {
		tmp, error := strconv.ParseFloat(str, 64)
		if error != nil {
			panic("not a number")
		}
		tmp *= context.units * context.xStep
		if context.relative {
			context.x += tmp
		} else {
			context.x = tmp
		}
		return uint8(context.x / 256), uint8(context.x), true
	}
	filter["Y"] = func(str string) (byte, byte, bool) {
		tmp, error := strconv.ParseFloat(str, 64)
		if error != nil {
			panic("not a number")
		}
		tmp *= context.yStep
		if context.relative {
			context.y += tmp
		} else {
			context.y = tmp
		}
		return uint8(context.y / 256), uint8(context.y), true
	}
	filter["S"] = func(str string) (byte, byte, bool) {
		tmp, error := strconv.ParseInt(str, 10, 16)
		if error != nil {
			panic("not a number")
		}
		return uint8(tmp / 256), uint8(tmp), true
	}
	filter["M"] = filter["S"]
	filter["V"] = func(str string) (byte, byte, bool) {
		tmp, error := strconv.ParseFloat(str, 64)
		context.v = tmp
		if error != nil {
			panic("not a number")
		}
		tmp *= 100
		return uint8(tmp / 256), uint8(tmp), true
	}
	filter["A"] = func(str string) (byte, byte, bool) {
		tmp, error := strconv.ParseFloat(str, 64)
		context.a = tmp
		if error != nil {
			panic("not a number")
		}
		tmp *= 1000
		return uint8(tmp / 256), uint8(tmp), true
	}

	context.xStep = 100
	context.yStep = 10
	context.units = 1
}
func main() {
	setup()

	fmt.Println(execute("G0 X1.4 Y1.2123")) //move to 1.4 1.2123
	fmt.Println(execute("G91"))             //set relative
	fmt.Println(execute("G0 X1.4"))         //move to 2.8 1.2123
}
func execute(line string) []byte {
	tokens := strings.Split(line, " ")
	switch tokens[0] {
	case "G0", "G1":
		return compile([]string{"Y", "X"}, 1, tokens, filter)
	case "G4":
		return compile([]string{"S", "M"}, 3, tokens, filter)
	case "G20":
		setScale(2.6)
	case "G21":
		setScale(1.0)
	case "G90":
		setRelative(false)
	case "G91":
		setRelative(true)
	case "M0":
		tmp := make([]byte, 2)
		tmp[0] = 1
		tmp[1] = 5
		return tmp
	case "M1":
		tmp := make([]byte, 2)
		tmp[0] = 1
		tmp[1] = 6
		return tmp
	case "M3":
		context.tubeOn = true
		return compile([]string{"V", "A"}, 7, tokens, filter)
	case "M5":
		context.tubeOn = false
		tmp := make([]byte, 2)
		tmp[0] = 1
		tmp[1] = 8
		return tmp
	case "M100":
		tmp := make([]byte, 2)
		tmp[0] = 1
		tmp[1] = 9
		return tmp
	case "M102":
		tmp := make([]byte, 2)
		tmp[0] = 1
		tmp[1] = 0
		return tmp
	default:
		panic("Invalid command")
	}
	return nil
}

func setScale(scale float64) {
	context.units = scale
}
func setRelative(relative bool) {
	context.relative = relative
}

func compile(flags []string, code byte, tokens []string, filter map[string]func(string) (byte, byte, bool)) []byte {
	args := Parse(tokens, flags)
	out := make([]byte, 2)

	for i, arg := range args {
		first, second, set := filter[i](arg)
		if set {
			out = append(out, first, second)
		}
		out[1] += 1 << uint(IndexOf(i, flags))
	}

	out[0] = byte(len(out) - 1)

	out[1] = out[1]<<4 + code

	return out
}
func Parse(tokens []string, flags []string) map[string]string {
	out := make(map[string]string)
	for _, token := range tokens {
		for _, flag := range flags {
			if strings.HasPrefix(token, flag) {
				out[flag] = strings.TrimPrefix(token, flag)
			}
		}
	}
	return out
}
func IndexOf(elem string, array []string) int {
	for i, el := range array {
		if el == elem {
			return i
		}
	}
	return -1
}
<|endoftext|>


<commit_before>package GoHPACK

func PackIntRepresentation(I uint32, N byte) (buf []byte) {
	if I < uint32(1<<N)-1 {
		return []byte{byte(I)}
	}

	I -= uint32(1<<N) - 1
	var i int = 1
	tmpI := I
	for ; tmpI >= 128; i++ {
		tmpI = tmpI >> 7
	} // check length

	buf = make([]byte, i+1)
	buf[0] = byte(1<<N) - 1
	i = 1
	for ; I >= 0x80; i++ {
		buf[i] = (byte(I) & 0x7f) | 0x80
		I = I >> 7
	}
	buf[i] = byte(I)

	return buf

}

func Encode(Headers []Header, fromStaticTable, fromDynamicTable, toHuffman bool, table *Table, dynamicTableSize int) (Wire []byte) {
	if dynamicTableSize != -1 {
		table.SetDynamicTableSize(uint32(dynamicTableSize))
		Wire = PackIntRepresentation(uint32(dynamicTableSize), 5)
		Wire[0] |= 0x20
	}

	for _, header := range Headers {
		match, index := table.FindHeader(header)
		if fromStaticTable && match {
			if fromDynamicTable {
				intRep := PackIntRepresentation(uint32(index), 7)
				intRep[0] |= 0x80
				Wire = append(Wire, intRep...)
			} else {
				intRep := PackIntRepresentation(uint32(index), 4)
				Wire = append(append(Wire, intRep...),
					table.PackContent(header.Value, toHuffman)...)
			}
		} else if fromStaticTable && !match && index > 0 {
			var intRep []byte
			if fromDynamicTable {
				intRep = PackIntRepresentation(uint32(index), 6)
				intRep[0] |= 0x40
				table.AddHeader(header)
			} else {
				intRep = PackIntRepresentation(uint32(index), 4)
			}
			Wire = append(append(Wire, intRep...),
				table.PackContent(header.Value, toHuffman)...)
		} else {
			var prefix byte = 0x00
			if fromDynamicTable {
				prefix = 0x40
				table.AddHeader(header)
			}
			content := append(table.PackContent(header.Name, toHuffman),
				table.PackContent(header.Value, toHuffman)...)
			Wire = append(append(Wire, prefix), content...)
		}
	}

	return
}

func ParseIntRepresentation(buf []byte, N byte) (I, cursor uint32) {
	I = uint32(buf[0] & ((1 << N) - 1)) // byte could be used as byte
	cursor = 1
	if I < ((1 << N) - 1) {
		return I, cursor
	}

	var M byte = 0
	for (buf[cursor] & 0x80) > 0 {
		I += uint32(buf[cursor]&0x7f) * (1 << M)
		M += 7
		cursor += 1
	}
	I += uint32(buf[cursor]&0x7f) * (1 << M)
	return I, cursor + 1

}

func Decode(buf []byte, table *Table) (Headers []Header) {
	var cursor uint32 = 0
	for cursor < uint32(len(buf)) {
		isIndexed := false
		if buf[cursor]&0xe0 == 0x20 {
			// 7.3 Header Table Size Update
			size, c := ParseIntRepresentation(buf[cursor:], 5)
			table.SetDynamicTableSize(size)
			cursor += c
		}

		var nLen byte
		if (buf[cursor] & 0x80) > 0 {
			// 7.1 Indexed Header Field
			if (buf[cursor] & 0x7f) == 0 {
				panic('a')
			}
			nLen = 7
			isIndexed = true
		} else {
			if buf[cursor]&0xc0 == 0x40 {
				// 7.2.1 Literal Header Field with Incremental Indexing
				nLen = 6
			} else {
				// when buf[cursor]&0xf0 == 0xf0
				// 7.2.2 Literal Header Field without Indexing
				// else
				// 7.2.3 Literal Header Field never Indexed
				nLen = 4
			}
		}
		index, c1 := ParseIntRepresentation(buf[cursor:], nLen)
		name, value, c2 := table.ParseHeader(index, buf[cursor+c1:], isIndexed)
		cursor += c1 + c2
		header := Header{name, value}
		if nLen == 6 {
			table.AddHeader(header)
		}
		Headers = append(Headers, header)
	}

	return
}
<commit_msg>add one condition<commit_after>package GoHPACK

func PackIntRepresentation(I uint32, N byte) (buf []byte) {
	if I < uint32(1<<N)-1 {
		return []byte{byte(I)}
	}

	I -= uint32(1<<N) - 1
	var i int = 1
	tmpI := I
	for ; tmpI >= 128; i++ {
		tmpI = tmpI >> 7
	} // check length

	buf = make([]byte, i+1)
	buf[0] = byte(1<<N) - 1
	i = 1
	for ; I >= 0x80; i++ {
		buf[i] = (byte(I) & 0x7f) | 0x80
		I = I >> 7
	}
	buf[i] = byte(I)

	return buf

}

func Encode(Headers []Header, fromStaticTable, fromDynamicTable, toHuffman bool, table *Table, dynamicTableSize int) (Wire []byte) {
	if dynamicTableSize != -1 && int(table.dynamicTableSize) != dynamicTableSize {
		table.SetDynamicTableSize(uint32(dynamicTableSize))
		Wire = PackIntRepresentation(uint32(dynamicTableSize), 5)
		Wire[0] |= 0x20
	}

	for _, header := range Headers {
		match, index := table.FindHeader(header)
		if fromStaticTable && match {
			if fromDynamicTable {
				intRep := PackIntRepresentation(uint32(index), 7)
				intRep[0] |= 0x80
				Wire = append(Wire, intRep...)
			} else {
				intRep := PackIntRepresentation(uint32(index), 4)
				Wire = append(append(Wire, intRep...),
					table.PackContent(header.Value, toHuffman)...)
			}
		} else if fromStaticTable && !match && index > 0 {
			var intRep []byte
			if fromDynamicTable {
				intRep = PackIntRepresentation(uint32(index), 6)
				intRep[0] |= 0x40
				table.AddHeader(header)
			} else {
				intRep = PackIntRepresentation(uint32(index), 4)
			}
			Wire = append(append(Wire, intRep...),
				table.PackContent(header.Value, toHuffman)...)
		} else {
			var prefix byte = 0x00
			if fromDynamicTable {
				prefix = 0x40
				table.AddHeader(header)
			}
			content := append(table.PackContent(header.Name, toHuffman),
				table.PackContent(header.Value, toHuffman)...)
			Wire = append(append(Wire, prefix), content...)
		}
	}

	return
}

func ParseIntRepresentation(buf []byte, N byte) (I, cursor uint32) {
	I = uint32(buf[0] & ((1 << N) - 1)) // byte could be used as byte
	cursor = 1
	if I < ((1 << N) - 1) {
		return I, cursor
	}

	var M byte = 0
	for (buf[cursor] & 0x80) > 0 {
		I += uint32(buf[cursor]&0x7f) * (1 << M)
		M += 7
		cursor += 1
	}
	I += uint32(buf[cursor]&0x7f) * (1 << M)
	return I, cursor + 1

}

func Decode(buf []byte, table *Table) (Headers []Header) {
	var cursor uint32 = 0
	for cursor < uint32(len(buf)) {
		isIndexed := false
		if buf[cursor]&0xe0 == 0x20 {
			// 7.3 Header Table Size Update
			size, c := ParseIntRepresentation(buf[cursor:], 5)
			table.SetDynamicTableSize(size)
			cursor += c
		}

		var nLen byte
		if (buf[cursor] & 0x80) > 0 {
			// 7.1 Indexed Header Field
			if (buf[cursor] & 0x7f) == 0 {
				panic('a')
			}
			nLen = 7
			isIndexed = true
		} else {
			if buf[cursor]&0xc0 == 0x40 {
				// 7.2.1 Literal Header Field with Incremental Indexing
				nLen = 6
			} else {
				// when buf[cursor]&0xf0 == 0xf0
				// 7.2.2 Literal Header Field without Indexing
				// else
				// 7.2.3 Literal Header Field never Indexed
				nLen = 4
			}
		}
		index, c1 := ParseIntRepresentation(buf[cursor:], nLen)
		name, value, c2 := table.ParseHeader(index, buf[cursor+c1:], isIndexed)
		cursor += c1 + c2
		header := Header{name, value}
		if nLen == 6 {
			table.AddHeader(header)
		}
		Headers = append(Headers, header)
	}

	return
}
<|endoftext|>


<commit_before>package main

import "fmt"

func main(){
	fmt.Println("Hei\nVi er gruppe 12 og består av:\n")
	fmt.Println("Marius")
}
<commit_msg>Endring<commit_after>package main

import "fmt"

func main() {
	fmt.Println("Hei\nVi er gruppe 12 og består av:\n")
	fmt.Println("Marius")
	fmt.Println("Indra")
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"github.com/BigTong/cproxy"
	"github.com/elazarl/goproxy"
	"github.com/xlvector/cproxy"
	"log"
	"net/http"
	"time"
)

func main() {
	proxyPort := flag.String("proxyPort", "7182", "port of cproxy")
	randcodePort := flag.String("randcodePort", "9100", "port of randcoe server")
	flag.Parse()
	proxy := goproxy.NewProxyHttpServer()
	proxy.Verbose = true
	proxy.OnRequest().Do(&cproxy.RequestHandler{})
	proxy.OnResponse().Do(&cproxy.RespHandler{})

	randCodeServer := &cproxy.RandCodeServer{}
	http.Handle("/randcode", randCodeServer)
	s := &http.Server{
		Addr:           ":" + *randcodePort,
		ReadTimeout:    30 * time.Second,
		WriteTimeout:   40 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}
	go s.ListenAndServe()

	log.Fatal(http.ListenAndServe(":"+*proxyPort, proxy))
}
<commit_msg>delete import<commit_after>package main

import (
	"flag"
	"github.com/elazarl/goproxy"
	"github.com/xlvector/cproxy"
	"log"
	"net/http"
	"time"
)

func main() {
	proxyPort := flag.String("proxyPort", "7182", "port of cproxy")
	randcodePort := flag.String("randcodePort", "9100", "port of randcoe server")
	flag.Parse()
	proxy := goproxy.NewProxyHttpServer()
	proxy.Verbose = true
	proxy.OnRequest().Do(&cproxy.RequestHandler{})
	proxy.OnResponse().Do(&cproxy.RespHandler{})

	randCodeServer := &cproxy.RandCodeServer{}
	http.Handle("/randcode", randCodeServer)
	s := &http.Server{
		Addr:           ":" + *randcodePort,
		ReadTimeout:    30 * time.Second,
		WriteTimeout:   40 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}
	go s.ListenAndServe()

	log.Fatal(http.ListenAndServe(":"+*proxyPort, proxy))
}
<|endoftext|>


<commit_before>package proxy

import "net"
import "io"

type Proxy struct {
	ErrorChannel chan error
	ListenFunc   func() (net.Listener, error)
	DialFunc     func() (net.Conn, error)

	Listener *net.Listener
}

func New(listenFunc func() (net.Listener, error), dialFunc func() (net.Conn, error)) *Proxy {
	p := new(Proxy)

	p.ErrorChannel = make(chan error)
	p.ListenFunc = listenFunc
	p.DialFunc = dialFunc

	return p
}

func (p *Proxy) Start() {
	listener, err := p.ListenFunc()
	p.Listener = &listener
	if err != nil {
		p.ErrorChannel <- err
		return
	}

	p.ErrorChannel <- nil

	for {
		clientConn, err := listener.Accept()
		if err != nil {
			panic(err)
		}
		go p.ForwardConnection(clientConn)
	}
}

func (p *Proxy) Stop() {
	if *p.Listener != nil {
		(*p.Listener).Close()
	}
}

func (p *Proxy) ForwardConnection(clientConn net.Conn) {
	defer clientConn.Close()
	serverConn, err := p.DialFunc()
	if err != nil {
		panic(err)
	}
	defer serverConn.Close()
	complete := make(chan bool)
	go Copy(serverConn, clientConn, complete)
	go Copy(clientConn, serverConn, complete)
	<-complete
	<-complete
}

func Copy(to net.Conn, from net.Conn, complete chan bool) {
	io.Copy(to, from)
	CloseWrite(to)
	complete <- true
}

func CloseWrite(rwc net.Conn) {
	if tcpc, ok := rwc.(*net.TCPConn); ok {
		tcpc.CloseWrite()
	} else if unixc, ok := rwc.(*net.UnixConn); ok {
		unixc.CloseWrite()
	}
}
<commit_msg>Proxy closes client conn immediately if there's an upstream conn error<commit_after>package proxy

import "net"
import "io"
import "fmt"

type Proxy struct {
	ErrorChannel chan error
	ListenFunc   func() (net.Listener, error)
	DialFunc     func() (net.Conn, error)

	Listener *net.Listener
}

func New(listenFunc func() (net.Listener, error), dialFunc func() (net.Conn, error)) *Proxy {
	p := new(Proxy)

	p.ErrorChannel = make(chan error)
	p.ListenFunc = listenFunc
	p.DialFunc = dialFunc

	return p
}

func (p *Proxy) Start() {
	listener, err := p.ListenFunc()
	p.Listener = &listener
	if err != nil {
		p.ErrorChannel <- err
		return
	}

	p.ErrorChannel <- nil

	for {
		clientConn, err := listener.Accept()
		if err != nil {
			panic(err)
		}
		go p.ForwardConnection(clientConn)
	}
}

func (p *Proxy) Stop() {
	if *p.Listener != nil {
		(*p.Listener).Close()
	}
}

func (p *Proxy) ForwardConnection(clientConn net.Conn) {
	defer clientConn.Close()
	serverConn, err := p.DialFunc()
	if err != nil {
		fmt.Println(err)
		clientConn.Close()
		return
	}
	defer serverConn.Close()
	complete := make(chan bool)
	go Copy(serverConn, clientConn, complete)
	go Copy(clientConn, serverConn, complete)
	<-complete
	<-complete
}

func Copy(to net.Conn, from net.Conn, complete chan bool) {
	io.Copy(to, from)
	CloseWrite(to)
	complete <- true
}

func CloseWrite(rwc net.Conn) {
	if tcpc, ok := rwc.(*net.TCPConn); ok {
		tcpc.CloseWrite()
	} else if unixc, ok := rwc.(*net.UnixConn); ok {
		unixc.CloseWrite()
	}
}
<|endoftext|>


<commit_before><commit_msg>latest<commit_after><|endoftext|>


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

// Repository contents API methods.
// http://developer.github.com/v3/repos/contents/

package github

import (
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
)

// RepositoryContent represents a file or directory in a github repository.
type RepositoryContent struct {
	Type        *string `json:"type,omitempty"`
	Encoding    *string `json:"encoding,omitempty"`
	Size        *int    `json:"size,omitempty"`
	Name        *string `json:"name,omitempty"`
	Path        *string `json:"path,omitempty"`
	Content     *string `json:"content,omitempty"`
	SHA         *string `json:"sha,omitempty"`
	URL         *string `json:"url,omitempty"`
	GitURL      *string `json:"git_url,omitempty"`
	HTMLURL     *string `json:"html_url,omitempty"`
	DownloadURL *string `json:"download_url,omitempty"`
}

// RepositoryContentResponse holds the parsed response from CreateFile, UpdateFile, and DeleteFile.
type RepositoryContentResponse struct {
	Content *RepositoryContent `json:"content,omitempty"`
	Commit  `json:"commit,omitempty"`
}

// RepositoryContentFileOptions specifies optional parameters for CreateFile, UpdateFile, and DeleteFile.
type RepositoryContentFileOptions struct {
	Message   *string       `json:"message,omitempty"`
	Content   []byte        `json:"content,omitempty"`
	SHA       *string       `json:"sha,omitempty"`
	Branch    *string       `json:"branch,omitempty"`
	Author    *CommitAuthor `json:"author,omitempty"`
	Committer *CommitAuthor `json:"committer,omitempty"`
}

// RepositoryContentGetOptions represents an optional ref parameter, which can be a SHA,
// branch, or tag
type RepositoryContentGetOptions struct {
	Ref string `url:"ref,omitempty"`
}

func (r RepositoryContent) String() string {
	return Stringify(r)
}

// Decode decodes the file content if it is base64 encoded.
func (r *RepositoryContent) Decode() ([]byte, error) {
	if *r.Encoding != "base64" {
		return nil, errors.New("cannot decode non-base64")
	}
	o, err := base64.StdEncoding.DecodeString(*r.Content)
	if err != nil {
		return nil, err
	}
	return o, nil
}

// GetReadme gets the Readme file for the repository.
//
// GitHub API docs: http://developer.github.com/v3/repos/contents/#get-the-readme
func (s *RepositoriesService) GetReadme(owner, repo string, opt *RepositoryContentGetOptions) (*RepositoryContent, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/readme", owner, repo)
	u, err := addOptions(u, opt)
	if err != nil {
		return nil, nil, err
	}
	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}
	readme := new(RepositoryContent)
	resp, err := s.client.Do(req, readme)
	if err != nil {
		return nil, resp, err
	}
	return readme, resp, err
}

// GetContents can return either the metadata and content of a single file
// (when path references a file) or the metadata of all the files and/or
// subdirectories of a directory (when path references a directory). To make it
// easy to distinguish between both result types and to mimic the API as much
// as possible, both result types will be returned but only one will contain a
// value and the other will be nil.
//
// GitHub API docs: http://developer.github.com/v3/repos/contents/#get-contents
func (s *RepositoriesService) GetContents(owner, repo, path string, opt *RepositoryContentGetOptions) (fileContent *RepositoryContent,
	directoryContent []*RepositoryContent, resp *Response, err error) {
	u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path)
	u, err = addOptions(u, opt)
	if err != nil {
		return nil, nil, nil, err
	}
	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, nil, err
	}
	var rawJSON json.RawMessage
	resp, err = s.client.Do(req, &rawJSON)
	if err != nil {
		return nil, nil, resp, err
	}
	fileUnmarshalError := json.Unmarshal(rawJSON, &fileContent)
	if fileUnmarshalError == nil {
		return fileContent, nil, resp, fileUnmarshalError
	}
	directoryUnmarshalError := json.Unmarshal(rawJSON, &directoryContent)
	if directoryUnmarshalError == nil {
		return nil, directoryContent, resp, directoryUnmarshalError
	}
	return nil, nil, resp, fmt.Errorf("unmarshalling failed for both file and directory content: %s and %s ", fileUnmarshalError, directoryUnmarshalError)
}

// CreateFile creates a new file in a repository at the given path and returns
// the commit and file metadata.
//
// GitHub API docs: http://developer.github.com/v3/repos/contents/#create-a-file
func (s *RepositoriesService) CreateFile(owner, repo, path string, opt *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) {
	u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path)
	req, err := s.client.NewRequest("PUT", u, opt)
	if err != nil {
		return nil, nil, err
	}
	createResponse := new(RepositoryContentResponse)
	resp, err := s.client.Do(req, createResponse)
	if err != nil {
		return nil, resp, err
	}
	return createResponse, resp, err
}

// UpdateFile updates a file in a repository at the given path and returns the
// commit and file metadata. Requires the blob SHA of the file being updated.
//
// GitHub API docs: http://developer.github.com/v3/repos/contents/#update-a-file
func (s *RepositoriesService) UpdateFile(owner, repo, path string, opt *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) {
	u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path)
	req, err := s.client.NewRequest("PUT", u, opt)
	if err != nil {
		return nil, nil, err
	}
	updateResponse := new(RepositoryContentResponse)
	resp, err := s.client.Do(req, updateResponse)
	if err != nil {
		return nil, resp, err
	}
	return updateResponse, resp, err
}

// DeleteFile deletes a file from a repository and returns the commit.
// Requires the blob SHA of the file to be deleted.
//
// GitHub API docs: http://developer.github.com/v3/repos/contents/#delete-a-file
func (s *RepositoriesService) DeleteFile(owner, repo, path string, opt *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) {
	u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path)
	req, err := s.client.NewRequest("DELETE", u, opt)
	if err != nil {
		return nil, nil, err
	}
	deleteResponse := new(RepositoryContentResponse)
	resp, err := s.client.Do(req, deleteResponse)
	if err != nil {
		return nil, resp, err
	}
	return deleteResponse, resp, err
}

// archiveFormat is used to define the archive type when calling GetArchiveLink.
type archiveFormat string

const (
	// Tarball specifies an archive in gzipped tar format.
	Tarball archiveFormat = "tarball"

	// Zipball specifies an archive in zip format.
	Zipball archiveFormat = "zipball"
)

// GetArchiveLink returns an URL to download a tarball or zipball archive for a
// repository. The archiveFormat can be specified by either the github.Tarball
// or github.Zipball constant.
//
// GitHub API docs: http://developer.github.com/v3/repos/contents/#get-archive-link
func (s *RepositoriesService) GetArchiveLink(owner, repo string, archiveformat archiveFormat, opt *RepositoryContentGetOptions) (*url.URL, *Response, error) {
	u := fmt.Sprintf("repos/%s/%s/%s", owner, repo, archiveformat)
	if opt != nil && opt.Ref != "" {
		u += fmt.Sprintf("/%s", opt.Ref)
	}
	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}
	var resp *http.Response
	// Use http.DefaultTransport if no custom Transport is configured
	if s.client.client.Transport == nil {
		resp, err = http.DefaultTransport.RoundTrip(req)
	} else {
		resp, err = s.client.client.Transport.RoundTrip(req)
	}
	if err != nil || resp.StatusCode != http.StatusFound {
		return nil, newResponse(resp), err
	}
	parsedURL, err := url.Parse(resp.Header.Get("Location"))
	return parsedURL, newResponse(resp), err
}
<commit_msg>This adds an additional method to RepositoriesService to download a file<commit_after>// Copyright 2013 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Repository contents API methods.
// http://developer.github.com/v3/repos/contents/

package github

import (
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"path"
)

// RepositoryContent represents a file or directory in a github repository.
type RepositoryContent struct {
	Type        *string `json:"type,omitempty"`
	Encoding    *string `json:"encoding,omitempty"`
	Size        *int    `json:"size,omitempty"`
	Name        *string `json:"name,omitempty"`
	Path        *string `json:"path,omitempty"`
	Content     *string `json:"content,omitempty"`
	SHA         *string `json:"sha,omitempty"`
	URL         *string `json:"url,omitempty"`
	GitURL      *string `json:"git_url,omitempty"`
	HTMLURL     *string `json:"html_url,omitempty"`
	DownloadURL *string `json:"download_url,omitempty"`
}

// RepositoryContentResponse holds the parsed response from CreateFile, UpdateFile, and DeleteFile.
type RepositoryContentResponse struct {
	Content *RepositoryContent `json:"content,omitempty"`
	Commit  `json:"commit,omitempty"`
}

// RepositoryContentFileOptions specifies optional parameters for CreateFile, UpdateFile, and DeleteFile.
type RepositoryContentFileOptions struct {
	Message   *string       `json:"message,omitempty"`
	Content   []byte        `json:"content,omitempty"`
	SHA       *string       `json:"sha,omitempty"`
	Branch    *string       `json:"branch,omitempty"`
	Author    *CommitAuthor `json:"author,omitempty"`
	Committer *CommitAuthor `json:"committer,omitempty"`
}

// RepositoryContentGetOptions represents an optional ref parameter, which can be a SHA,
// branch, or tag
type RepositoryContentGetOptions struct {
	Ref string `url:"ref,omitempty"`
}

func (r RepositoryContent) String() string {
	return Stringify(r)
}

// Decode decodes the file content if it is base64 encoded.
func (r *RepositoryContent) Decode() ([]byte, error) {
	if *r.Encoding != "base64" {
		return nil, errors.New("cannot decode non-base64")
	}
	o, err := base64.StdEncoding.DecodeString(*r.Content)
	if err != nil {
		return nil, err
	}
	return o, nil
}

// GetReadme gets the Readme file for the repository.
//
// GitHub API docs: http://developer.github.com/v3/repos/contents/#get-the-readme
func (s *RepositoriesService) GetReadme(owner, repo string, opt *RepositoryContentGetOptions) (*RepositoryContent, *Response, error) {
	u := fmt.Sprintf("repos/%v/%v/readme", owner, repo)
	u, err := addOptions(u, opt)
	if err != nil {
		return nil, nil, err
	}
	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}
	readme := new(RepositoryContent)
	resp, err := s.client.Do(req, readme)
	if err != nil {
		return nil, resp, err
	}
	return readme, resp, err
}

// DownloadContents returns an io.ReadCloser that reads the contents of the
// specified file. This function will work with files of any size, as opposed
// to GetContents which is limited to 1 Mb files. It is the caller's
// responsibility to close the ReadCloser.
func (s *RepositoriesService) DownloadContents(owner, repo, filepath string, opt *RepositoryContentGetOptions) (io.ReadCloser, error) {
	dir := path.Dir(filepath)
	filename := path.Base(filepath)
	_, dirContents, _, err := s.GetContents(owner, repo, dir, opt)
	if err != nil {
		return nil, err
	}
	for _, contents := range dirContents {
		if *contents.Name == filename {
			if contents.DownloadURL == nil || *contents.DownloadURL == "" {
				return nil, fmt.Errorf("No download link found for %s", filepath)
			}
			resp, err := s.client.client.Get(*contents.DownloadURL)
			if err != nil {
				return nil, err
			}
			return resp.Body, nil
		}
	}
	return nil, fmt.Errorf("No file named %s found in %s", filename, dir)
}

// GetContents can return either the metadata and content of a single file
// (when path references a file) or the metadata of all the files and/or
// subdirectories of a directory (when path references a directory). To make it
// easy to distinguish between both result types and to mimic the API as much
// as possible, both result types will be returned but only one will contain a
// value and the other will be nil.
//
// GitHub API docs: http://developer.github.com/v3/repos/contents/#get-contents
func (s *RepositoriesService) GetContents(owner, repo, path string, opt *RepositoryContentGetOptions) (fileContent *RepositoryContent,
	directoryContent []*RepositoryContent, resp *Response, err error) {
	u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path)
	u, err = addOptions(u, opt)
	if err != nil {
		return nil, nil, nil, err
	}
	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, nil, err
	}
	var rawJSON json.RawMessage
	resp, err = s.client.Do(req, &rawJSON)
	if err != nil {
		return nil, nil, resp, err
	}
	fileUnmarshalError := json.Unmarshal(rawJSON, &fileContent)
	if fileUnmarshalError == nil {
		return fileContent, nil, resp, fileUnmarshalError
	}
	directoryUnmarshalError := json.Unmarshal(rawJSON, &directoryContent)
	if directoryUnmarshalError == nil {
		return nil, directoryContent, resp, directoryUnmarshalError
	}
	return nil, nil, resp, fmt.Errorf("unmarshalling failed for both file and directory content: %s and %s ", fileUnmarshalError, directoryUnmarshalError)
}

// CreateFile creates a new file in a repository at the given path and returns
// the commit and file metadata.
//
// GitHub API docs: http://developer.github.com/v3/repos/contents/#create-a-file
func (s *RepositoriesService) CreateFile(owner, repo, path string, opt *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) {
	u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path)
	req, err := s.client.NewRequest("PUT", u, opt)
	if err != nil {
		return nil, nil, err
	}
	createResponse := new(RepositoryContentResponse)
	resp, err := s.client.Do(req, createResponse)
	if err != nil {
		return nil, resp, err
	}
	return createResponse, resp, err
}

// UpdateFile updates a file in a repository at the given path and returns the
// commit and file metadata. Requires the blob SHA of the file being updated.
//
// GitHub API docs: http://developer.github.com/v3/repos/contents/#update-a-file
func (s *RepositoriesService) UpdateFile(owner, repo, path string, opt *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) {
	u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path)
	req, err := s.client.NewRequest("PUT", u, opt)
	if err != nil {
		return nil, nil, err
	}
	updateResponse := new(RepositoryContentResponse)
	resp, err := s.client.Do(req, updateResponse)
	if err != nil {
		return nil, resp, err
	}
	return updateResponse, resp, err
}

// DeleteFile deletes a file from a repository and returns the commit.
// Requires the blob SHA of the file to be deleted.
//
// GitHub API docs: http://developer.github.com/v3/repos/contents/#delete-a-file
func (s *RepositoriesService) DeleteFile(owner, repo, path string, opt *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) {
	u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path)
	req, err := s.client.NewRequest("DELETE", u, opt)
	if err != nil {
		return nil, nil, err
	}
	deleteResponse := new(RepositoryContentResponse)
	resp, err := s.client.Do(req, deleteResponse)
	if err != nil {
		return nil, resp, err
	}
	return deleteResponse, resp, err
}

// archiveFormat is used to define the archive type when calling GetArchiveLink.
type archiveFormat string

const (
	// Tarball specifies an archive in gzipped tar format.
	Tarball archiveFormat = "tarball"

	// Zipball specifies an archive in zip format.
	Zipball archiveFormat = "zipball"
)

// GetArchiveLink returns an URL to download a tarball or zipball archive for a
// repository. The archiveFormat can be specified by either the github.Tarball
// or github.Zipball constant.
//
// GitHub API docs: http://developer.github.com/v3/repos/contents/#get-archive-link
func (s *RepositoriesService) GetArchiveLink(owner, repo string, archiveformat archiveFormat, opt *RepositoryContentGetOptions) (*url.URL, *Response, error) {
	u := fmt.Sprintf("repos/%s/%s/%s", owner, repo, archiveformat)
	if opt != nil && opt.Ref != "" {
		u += fmt.Sprintf("/%s", opt.Ref)
	}
	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}
	var resp *http.Response
	// Use http.DefaultTransport if no custom Transport is configured
	if s.client.client.Transport == nil {
		resp, err = http.DefaultTransport.RoundTrip(req)
	} else {
		resp, err = s.client.client.Transport.RoundTrip(req)
	}
	if err != nil || resp.StatusCode != http.StatusFound {
		return nil, newResponse(resp), err
	}
	parsedURL, err := url.Parse(resp.Header.Get("Location"))
	return parsedURL, newResponse(resp), err
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Intel Corporation
//
// 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 (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"regexp"
	"testing"

	"github.com/Sirupsen/logrus"
)

type testData struct {
	path          string
	expectFailure bool
}

func init() {
	// Ensure all log levels are logged
	ccLog.Level = logrus.DebugLevel

	// Discard "normal" log output: this test only cares about the
	// (additional) global log output
	ccLog.Out = ioutil.Discard
}

func grep(pattern, file string) error {
	if file == "" {
		return errors.New("need file")
	}

	bytes, err := ioutil.ReadFile(file)
	if err != nil {
		return err
	}

	re := regexp.MustCompile(pattern)
	matches := re.FindAllStringSubmatch(string(bytes), -1)

	if matches == nil {
		return fmt.Errorf("pattern %q not found in file %q", pattern, file)
	}

	return nil
}

func TestNewGlobalLogHook(t *testing.T) {
	tmpdir, err := ioutil.TempDir("", "")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmpdir)

	tmpfile := path.Join(tmpdir, "global.log")

	data := []testData{
		{"", true},
		{tmpfile, false},
	}

	for _, d := range data {
		hook, err := newGlobalLogHook(d.path)
		if d.expectFailure {
			if err == nil {
				t.Fatal(fmt.Sprintf("unexpected succes from newGlobalLogHook(path=%v)", d.path))
			}
		} else {
			if err != nil {
				t.Fatal(fmt.Sprintf("unexpected failure from newGlobalLogHook(path=%q): %v", d.path, err))
			}
			if hook.path != d.path {
				t.Fatal(fmt.Sprintf("expected hook to contain path %q, found %q", d.path, hook.path))
			}
		}
	}
}

func TestHandleGlobalLog(t *testing.T) {
	tmpdir, err := ioutil.TempDir("", "")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmpdir)

	tmpfile := path.Join(tmpdir, "global.log")

	data := []testData{
		{"", false},

		// path must be absolute, so these should fail
		{"foo/bar/global.log", true},
		{"../foo/bar/global.log", true},
		{"./foo/bar/global.log", true},

		{tmpfile, false},
	}

	for _, d := range data {
		err := handleGlobalLog(d.path)
		if d.expectFailure {
			if err == nil {
				t.Fatal(fmt.Sprintf("unexpected success from handleGlobalLog(path=%q)", d.path))
			}
			continue
		}

		if err != nil {
			t.Fatal(fmt.Sprintf("unexpected failure from handleGlobalLog(path=%q): %v", d.path, err))
		}

		// It's valid to pass a blank path to handleGlobalLog(),
		// but no point in checking for log entries in that
		// case!
		if d.path == "" {
			continue
		}

		// Add a log entry
		str := "hello. foo bar baz!"
		ccLog.Debug(str)

		// Check that the string was logged
		err = grep(fmt.Sprintf("debug:.*%s", str), d.path)
		if err != nil {
			t.Fatal(err)
		}

		// Check expected perms
		st, err := os.Stat(d.path)
		if err != nil {
			t.Fatal(err)
		}

		expectedPerms := "-rw-r-----"
		actualPerms := st.Mode().String()
		if expectedPerms != actualPerms {
			t.Fatal(fmt.Sprintf("logfile %v should have perms %v, but found %v",
				d.path, expectedPerms, actualPerms))
		}
	}
}

func TestHandleGlobalLogEnvVar(t *testing.T) {
	envvar := "CC_RUNTIME_GLOBAL_LOG"
	tmpdir, err := ioutil.TempDir("", "")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmpdir)

	tmpfile := path.Join(tmpdir, "global.log")
	tmpfile2 := path.Join(tmpdir, "global-envvar.log")

	os.Setenv(envvar, tmpfile2)
	defer os.Unsetenv(envvar)

	err = handleGlobalLog(tmpfile)
	if err != nil {
		t.Fatal(err)
	}

	str := "foo or moo?"
	ccLog.Debug(str)
	tmpfileExists := fileExists(tmpfile)
	tmpfile2Exists := fileExists(tmpfile2)

	if tmpfileExists == true {
		t.Fatal(fmt.Sprintf("tmpfile %q exists unexpectedly", tmpfile))
	}

	if tmpfile2Exists == false {
		t.Fatal(fmt.Sprintf("tmpfile2 %q does not exist unexpectedly", tmpfile2))
	}

	// Check that the string was logged
	err = grep(fmt.Sprintf("debug:.*%s", str), tmpfile2)
	if err != nil {
		t.Fatal(err)
	}
}
<commit_msg>tests: Increase logger test coverage to 100%.<commit_after>// Copyright (c) 2017 Intel Corporation
//
// 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 (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"regexp"
	"testing"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/stretchr/testify/assert"
)

type testData struct {
	path          string
	expectFailure bool
}

func init() {
	// Ensure all log levels are logged
	ccLog.Level = logrus.DebugLevel

	// Discard "normal" log output: this test only cares about the
	// (additional) global log output
	ccLog.Out = ioutil.Discard
}

func grep(pattern, file string) error {
	if file == "" {
		return errors.New("need file")
	}

	bytes, err := ioutil.ReadFile(file)
	if err != nil {
		return err
	}

	re := regexp.MustCompile(pattern)
	matches := re.FindAllStringSubmatch(string(bytes), -1)

	if matches == nil {
		return fmt.Errorf("pattern %q not found in file %q", pattern, file)
	}

	return nil
}

func TestNewGlobalLogHook(t *testing.T) {
	tmpdir, err := ioutil.TempDir("", "")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmpdir)

	tmpfile := path.Join(tmpdir, "global.log")

	data := []testData{
		{"", true},
		{tmpfile, false},
	}

	for _, d := range data {
		hook, err := newGlobalLogHook(d.path)
		if d.expectFailure {
			if err == nil {
				t.Fatal(fmt.Sprintf("unexpected succes from newGlobalLogHook(path=%v)", d.path))
			}
		} else {
			if err != nil {
				t.Fatal(fmt.Sprintf("unexpected failure from newGlobalLogHook(path=%q): %v", d.path, err))
			}
			if hook.path != d.path {
				t.Fatal(fmt.Sprintf("expected hook to contain path %q, found %q", d.path, hook.path))
			}
		}
	}
}

func TestHandleGlobalLog(t *testing.T) {
	tmpdir, err := ioutil.TempDir("", "")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmpdir)

	subDir := path.Join(tmpdir, "a/b/global.log")
	err = os.MkdirAll(subDir, testDirMode)
	assert.NoError(t, err)

	existingFile := path.Join(tmpdir, "c")
	err = createEmptyFile(existingFile)
	assert.NoError(t, err)

	tmpfile := path.Join(tmpdir, "global.log")

	data := []testData{
		{"", false},

		// path must be absolute, so these should fail
		{"foo/bar/global.log", true},
		{"../foo/bar/global.log", true},
		{"./foo/bar/global.log", true},
		{subDir, true},
		{path.Join(existingFile, "global.log"), true},

		{tmpfile, false},
	}

	for _, d := range data {
		err := handleGlobalLog(d.path)
		if d.expectFailure {
			if err == nil {
				t.Fatal(fmt.Sprintf("unexpected success from handleGlobalLog(path=%q)", d.path))
			}
			continue
		}

		if err != nil {
			t.Fatal(fmt.Sprintf("unexpected failure from handleGlobalLog(path=%q): %v", d.path, err))
		}

		// It's valid to pass a blank path to handleGlobalLog(),
		// but no point in checking for log entries in that
		// case!
		if d.path == "" {
			continue
		}

		// Add a log entry
		str := "hello. foo bar baz!"
		ccLog.Debug(str)

		// Check that the string was logged
		err = grep(fmt.Sprintf("debug:.*%s", str), d.path)
		if err != nil {
			t.Fatal(err)
		}

		// Check expected perms
		st, err := os.Stat(d.path)
		if err != nil {
			t.Fatal(err)
		}

		expectedPerms := "-rw-r-----"
		actualPerms := st.Mode().String()
		if expectedPerms != actualPerms {
			t.Fatal(fmt.Sprintf("logfile %v should have perms %v, but found %v",
				d.path, expectedPerms, actualPerms))
		}
	}
}

func TestHandleGlobalLogEnvVar(t *testing.T) {
	envvar := "CC_RUNTIME_GLOBAL_LOG"
	tmpdir, err := ioutil.TempDir("", "")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmpdir)

	tmpfile := path.Join(tmpdir, "global.log")
	tmpfile2 := path.Join(tmpdir, "global-envvar.log")

	os.Setenv(envvar, tmpfile2)
	defer os.Unsetenv(envvar)

	err = handleGlobalLog(tmpfile)
	if err != nil {
		t.Fatal(err)
	}

	str := "foo or moo?"
	ccLog.Debug(str)
	tmpfileExists := fileExists(tmpfile)
	tmpfile2Exists := fileExists(tmpfile2)

	if tmpfileExists == true {
		t.Fatal(fmt.Sprintf("tmpfile %q exists unexpectedly", tmpfile))
	}

	if tmpfile2Exists == false {
		t.Fatal(fmt.Sprintf("tmpfile2 %q does not exist unexpectedly", tmpfile2))
	}

	// Check that the string was logged
	err = grep(fmt.Sprintf("debug:.*%s", str), tmpfile2)
	if err != nil {
		t.Fatal(err)
	}
}

func TestLoggerFire(t *testing.T) {
	tmpdir, err := ioutil.TempDir("", "")
	if err != nil {
		t.Fatal(err)
	}

	ccLog = logrus.New()

	logFile := path.Join(tmpdir, "a/b/global.log")
	err = handleGlobalLog(logFile)
	assert.NoError(t, err)

	//ccLog.Debug("foo")

	entry := &logrus.Entry{
		Logger:  ccLog,
		Time:    time.Now().UTC(),
		Level:   logrus.DebugLevel,
		Message: "foo",
	}

	err = ccLog.Hooks.Fire(logrus.DebugLevel, entry)
	assert.NoError(t, err)

	assert.Equal(t, len(ccLog.Hooks[logrus.DebugLevel]), 1)
	hook, ok := ccLog.Hooks[logrus.DebugLevel][0].(*GlobalLogHook)
	assert.True(t, ok)

	err = hook.file.Close()
	assert.NoError(t, err)

	err = os.RemoveAll(tmpdir)
	assert.NoError(t, err)

	err = ccLog.Hooks.Fire(logrus.DebugLevel, entry)
	assert.Error(t, err)
}
<|endoftext|>