<commit_before>package redis

import (
	"fmt"
	"strconv"

	"github.com/fagongzi/goetty"
)

const (
	// CR \r
	CR = '\r'
	// LF \n
	LF = '\n'
	// ARGBegin '$'
	ARGBegin = '$'
	// CMDBegin '*'
	CMDBegin = '*'

	defaultBufferSize = 64
)

var (
	// Delims delims
	Delims = []byte("\r\n")
	// NullBulk empty bulk string
	NullBulk = []byte("-1")
	// NullArray empty array
	NullArray = []byte("-1")
)

// ReadCommand returns redis command from buffer
func ReadCommand(in *goetty.ByteBuf) (bool, Command, error) {
	for {
		// remember the begin read index,
		// if we found has no enough data, we will resume this read index,
		// and waiting for next.
		backupReaderIndex := in.GetReaderIndex()

		c, err := in.ReadByte()
		if err != nil {
			return false, nil, err
		}

		// 1. Read ( *<number of arguments> CR LF )
		if c != CMDBegin {
			return false, nil, fmt.Errorf("illegal cmd char %d", c)
		}

		// 2. Read number of arguments
		count, argsCount, err := readStringInt(in)
		if count == 0 && err == nil {
			in.SetReaderIndex(backupReaderIndex)
			return false, nil, nil
		} else if err != nil {
			return false, nil, err
		}

		data := make([][]byte, argsCount)

		// 3. Read args
		for i := 0; i < argsCount; i++ {
			if 0 == in.Readable() {
				in.SetReaderIndex(backupReaderIndex)
				return false, nil, nil
			}

			// 3.1 Read ( $<number of bytes of argument 1> CR LF )
			c, err := in.ReadByte()
			if err != nil {
				return false, nil, err
			}

			// 3.2 Read ( *<number of arguments> CR LF )
			if c != ARGBegin {
				return false, nil, fmt.Errorf("illegal arg char %d", c)
			}

			count, argBytesCount, err := readStringInt(in)
			if count == 0 && err == nil {
				in.SetReaderIndex(backupReaderIndex)
				return false, nil, nil
			} else if err != nil {
				return false, nil, err
			} else if count < 2 {
				return false, nil, fmt.Errorf("illegal arg count: %d", count)
			}

			// 3.3  Read ( <argument data> CR LF )
			if argBytesCount+2 > in.Readable() {
				in.SetReaderIndex(backupReaderIndex)
				return false, nil, nil
			}
			count, value, err := in.ReadBytes(argBytesCount + 2)
			if count == 0 && err == nil {
				in.SetReaderIndex(backupReaderIndex)
				return false, nil, nil
			} else if err != nil {
				return false, nil, err
			}

			data[i] = value[:count-2]
		}

		return true, Command(data), nil
	}
}

func readCommandReply(in *goetty.ByteBuf) (bool, interface{}, error) {
	for {
		// remember the begin read index,
		// if we found has no enough data, we will resume this read index,
		// and waiting for next.
		backupReaderIndex := in.GetReaderIndex()
		cnt, line, err := readLine(in)
		if cnt == 0 && err == nil {
			in.SetReaderIndex(backupReaderIndex)
			return false, nil, nil
		} else if err != nil {
			return false, nil, err
		}

		line = line[:cnt-2]

		switch line[0] {
		case '+':
			return true, StatusResp(line[1:]), nil
		case '-':
			return true, ErrResp(line[1:]), nil
		case ':':
			return true, IntegerResp(line[1:]), nil
		case '$':
			size, err := parseInteger(line[1:])
			if err != nil {
				return false, nil, err
			}

			if size < 0 {
				return true, NullBulkResp(0), nil
			}

			// have not enough data, wait for next
			if size+2 > in.Readable() {
				in.SetReaderIndex(backupReaderIndex)
				return false, nil, nil
			}

			c, data, err := in.ReadBytes(size + 2)
			if err != nil {
				return false, nil, err
			}

			if c == 0 {
				in.SetReaderIndex(backupReaderIndex)
				return false, nil, nil
			}

			return true, BulkResp(data[:c-2]), nil
		case '*':
			size, err := parseInteger(line[1:])
			if err != nil {
				return false, nil, err
			}

			if size < 0 {
				return true, NullArrayResp(0), nil
			}

			r := make([]interface{}, size)
			for i := range r {
				complete, value, err := readCommandReply(in)
				if err != nil {
					return false, nil, err
				}

				if !complete {
					in.SetReaderIndex(backupReaderIndex)
					return false, nil, nil
				}

				r[i] = value
			}

			return true, r, nil
		default:
			return false, nil, fmt.Errorf("illegal cmd reply char %c", line[0])
		}
	}
}

func readStringInt(in *goetty.ByteBuf) (int, int, error) {
	count, line, err := readLine(in)
	if count == 0 && err == nil {
		return 0, 0, nil
	} else if err != nil {
		return 0, 0, err
	}

	// count-2:xclude 'CR CF'
	value, err := parseInteger(line[:count-2])
	if err != nil {
		return 0, 0, err
	}

	return len(line), value, nil
}

func readLine(in *goetty.ByteBuf) (int, []byte, error) {
	offset := 0
	size := in.Readable()

	for offset < size {
		ch, _ := in.PeekByte(offset)
		if ch == LF {
			ch, _ := in.PeekByte(offset - 1)
			if ch == CR {
				return in.ReadBytes(offset + 1)
			}

			return 0, nil, fmt.Errorf("illegal line end: %d", ch)
		}
		offset++
	}

	return 0, nil, nil
}

func parseInteger(data []byte) (int, error) {
	if data[0] == '-' && len(data) == 2 && data[1] == '1' {
		// handle $-1 and $-1 null replies.
		return -1, nil
	}

	return strconv.Atoi(goetty.SliceToString(data))
}
<commit_msg>fix: redis protocol<commit_after>package redis

import (
	"fmt"
	"strconv"

	"github.com/fagongzi/goetty"
)

const (
	// CR \r
	CR = '\r'
	// LF \n
	LF = '\n'
	// ARGBegin '$'
	ARGBegin = '$'
	// CMDBegin '*'
	CMDBegin = '*'

	defaultBufferSize = 64
)

var (
	// Delims delims
	Delims = []byte("\r\n")
	// NullBulk empty bulk string
	NullBulk = []byte("-1")
	// NullArray empty array
	NullArray = []byte("0")
)

// ReadCommand returns redis command from buffer
func ReadCommand(in *goetty.ByteBuf) (bool, Command, error) {
	for {
		// remember the begin read index,
		// if we found has no enough data, we will resume this read index,
		// and waiting for next.
		backupReaderIndex := in.GetReaderIndex()

		c, err := in.ReadByte()
		if err != nil {
			return false, nil, err
		}

		// 1. Read ( *<number of arguments> CR LF )
		if c != CMDBegin {
			return false, nil, fmt.Errorf("illegal cmd char %d", c)
		}

		// 2. Read number of arguments
		count, argsCount, err := readStringInt(in)
		if count == 0 && err == nil {
			in.SetReaderIndex(backupReaderIndex)
			return false, nil, nil
		} else if err != nil {
			return false, nil, err
		}

		data := make([][]byte, argsCount)

		// 3. Read args
		for i := 0; i < argsCount; i++ {
			if 0 == in.Readable() {
				in.SetReaderIndex(backupReaderIndex)
				return false, nil, nil
			}

			// 3.1 Read ( $<number of bytes of argument 1> CR LF )
			c, err := in.ReadByte()
			if err != nil {
				return false, nil, err
			}

			// 3.2 Read ( *<number of arguments> CR LF )
			if c != ARGBegin {
				return false, nil, fmt.Errorf("illegal arg char %d", c)
			}

			count, argBytesCount, err := readStringInt(in)
			if count == 0 && err == nil {
				in.SetReaderIndex(backupReaderIndex)
				return false, nil, nil
			} else if err != nil {
				return false, nil, err
			} else if count < 2 {
				return false, nil, fmt.Errorf("illegal arg count: %d", count)
			}

			// 3.3  Read ( <argument data> CR LF )
			if argBytesCount+2 > in.Readable() {
				in.SetReaderIndex(backupReaderIndex)
				return false, nil, nil
			}
			count, value, err := in.ReadBytes(argBytesCount + 2)
			if count == 0 && err == nil {
				in.SetReaderIndex(backupReaderIndex)
				return false, nil, nil
			} else if err != nil {
				return false, nil, err
			}

			data[i] = value[:count-2]
		}

		return true, Command(data), nil
	}
}

func readCommandReply(in *goetty.ByteBuf) (bool, interface{}, error) {
	for {
		// remember the begin read index,
		// if we found has no enough data, we will resume this read index,
		// and waiting for next.
		backupReaderIndex := in.GetReaderIndex()
		cnt, line, err := readLine(in)
		if cnt == 0 && err == nil {
			in.SetReaderIndex(backupReaderIndex)
			return false, nil, nil
		} else if err != nil {
			return false, nil, err
		}

		line = line[:cnt-2]

		switch line[0] {
		case '+':
			return true, StatusResp(line[1:]), nil
		case '-':
			return true, ErrResp(line[1:]), nil
		case ':':
			return true, IntegerResp(line[1:]), nil
		case '$':
			size, err := parseInteger(line[1:])
			if err != nil {
				return false, nil, err
			}

			if size < 0 {
				return true, NullBulkResp(0), nil
			}

			// have not enough data, wait for next
			if size+2 > in.Readable() {
				in.SetReaderIndex(backupReaderIndex)
				return false, nil, nil
			}

			c, data, err := in.ReadBytes(size + 2)
			if err != nil {
				return false, nil, err
			}

			if c == 0 {
				in.SetReaderIndex(backupReaderIndex)
				return false, nil, nil
			}

			return true, BulkResp(data[:c-2]), nil
		case '*':
			size, err := parseInteger(line[1:])
			if err != nil {
				return false, nil, err
			}

			if size < 0 {
				return true, NullArrayResp(0), nil
			}

			r := make([]interface{}, size)
			for i := range r {
				complete, value, err := readCommandReply(in)
				if err != nil {
					return false, nil, err
				}

				if !complete {
					in.SetReaderIndex(backupReaderIndex)
					return false, nil, nil
				}

				r[i] = value
			}

			return true, r, nil
		default:
			return false, nil, fmt.Errorf("illegal cmd reply char %c", line[0])
		}
	}
}

func readStringInt(in *goetty.ByteBuf) (int, int, error) {
	count, line, err := readLine(in)
	if count == 0 && err == nil {
		return 0, 0, nil
	} else if err != nil {
		return 0, 0, err
	}

	// count-2:xclude 'CR CF'
	value, err := parseInteger(line[:count-2])
	if err != nil {
		return 0, 0, err
	}

	return len(line), value, nil
}

func readLine(in *goetty.ByteBuf) (int, []byte, error) {
	offset := 0
	size := in.Readable()

	for offset < size {
		ch, _ := in.PeekByte(offset)
		if ch == LF {
			ch, _ := in.PeekByte(offset - 1)
			if ch == CR {
				return in.ReadBytes(offset + 1)
			}

			return 0, nil, fmt.Errorf("illegal line end: %d", ch)
		}
		offset++
	}

	return 0, nil, nil
}

func parseInteger(data []byte) (int, error) {
	if data[0] == '-' && len(data) == 2 && data[1] == '1' {
		// handle $-1 and $-1 null replies.
		return -1, nil
	}

	return strconv.Atoi(goetty.SliceToString(data))
}
<|endoftext|>


<commit_before>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package local

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/juju/schema"

	"github.com/juju/juju/agent"
	"github.com/juju/juju/container/kvm"
	"github.com/juju/juju/container/lxc"
	"github.com/juju/juju/environs/config"
	"github.com/juju/juju/instance"
)

var checkIfRoot = func() bool {
	return os.Getuid() == 0
}

var (
	NetworkBridgeKey = "network-bridge"
)
var (
	configFields = schema.Fields{
		"root-dir":       schema.String(),
		"bootstrap-ip":   schema.String(),
		NetworkBridgeKey: schema.String(),
		"container":      schema.String(),
		"storage-port":   schema.ForceInt(),
		"namespace":      schema.String(),
	}
	// The port defaults below are not entirely arbitrary.  Local user web
	// frameworks often use 8000 or 8080, so I didn't want to use either of
	// these, but did want the familiarity of using something in the 8000
	// range.
	configDefaults = schema.Defaults{
		"root-dir":       "",
		NetworkBridgeKey: "",
		"container":      string(instance.LXC),
		"bootstrap-ip":   schema.Omit,
		"storage-port":   8040,
		"namespace":      "",
	}
)

type environConfig struct {
	*config.Config
	attrs map[string]interface{}
}

func newEnvironConfig(config *config.Config, attrs map[string]interface{}) *environConfig {
	environConfig := &environConfig{
		Config: config,
		attrs:  attrs,
	}
	// Set correct default network bridge if needed
	// fix for http://pad.lv/1394450
	environConfig.setDefaultNetworkBridge()
	return environConfig
}

// Since it is technically possible for two different users on one machine to
// have the same local provider name, we need to have a simple way to
// namespace the file locations, but more importantly the containers.
func (c *environConfig) namespace() string {
	return c.attrs["namespace"].(string)
}

func (c *environConfig) rootDir() string {
	return c.attrs["root-dir"].(string)
}

func (c *environConfig) container() instance.ContainerType {
	return instance.ContainerType(c.attrs["container"].(string))
}

// setDefaultNetworkBridge sets default network bridge if none is provided.
// Default network bridge varies based on container type.
// Originally, default values were returned in getter. However,
// this meant that older clients were getting incorrect defaults
// (http://pad.lv/1394450)
func (c *environConfig) setDefaultNetworkBridge() {
	name := c.networkBridge()
	switch c.container() {
	case instance.LXC:
		if name == "" {
			name = lxc.DefaultLxcBridge
		}
	case instance.KVM:
		if name == "" || name == lxc.DefaultLxcBridge {
			// Older versions of juju used "lxcbr0" by default,
			// without checking the container type. See also
			// http://pad.lv/1307677.
			name = kvm.DefaultKvmBridge
		}
	}
	c.attrs[NetworkBridgeKey] = name
}

func (c *environConfig) networkBridge() string {
	// We don't care if it's not a string, because Validate takes care
	// of that.
	name, _ := c.attrs[NetworkBridgeKey].(string)
	return name
}

func (c *environConfig) storageDir() string {
	return filepath.Join(c.rootDir(), "storage")
}

func (c *environConfig) mongoDir() string {
	return filepath.Join(c.rootDir(), "db")
}

func (c *environConfig) logDir() string {
	return fmt.Sprintf("%s-%s", agent.DefaultLogDir, c.namespace())
}

// bootstrapIPAddress returns the IP address of the bootstrap machine.
// As of 1.18 this is only set inside the environment, and not in the
// .jenv file.
func (c *environConfig) bootstrapIPAddress() string {
	addr, _ := c.attrs["bootstrap-ip"].(string)
	return addr
}

func (c *environConfig) storagePort() int {
	return c.attrs["storage-port"].(int)
}

func (c *environConfig) storageAddr() string {
	return fmt.Sprintf("%s:%d", c.bootstrapIPAddress(), c.storagePort())
}

func (c *environConfig) configFile(filename string) string {
	return filepath.Join(c.rootDir(), filename)
}

func (c *environConfig) createDirs() error {
	for _, dirname := range []string{
		c.storageDir(),
		c.mongoDir(),
	} {
		logger.Tracef("creating directory %s", dirname)
		if err := os.MkdirAll(dirname, 0755); err != nil {
			return err
		}
	}
	return nil
}
<commit_msg>Review fix.<commit_after>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package local

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/juju/schema"

	"github.com/juju/juju/agent"
	"github.com/juju/juju/container/kvm"
	"github.com/juju/juju/container/lxc"
	"github.com/juju/juju/environs/config"
	"github.com/juju/juju/instance"
)

var checkIfRoot = func() bool {
	return os.Getuid() == 0
}

// Attribute keys
var NetworkBridgeKey = "network-bridge"

var (
	configFields = schema.Fields{
		"root-dir":       schema.String(),
		"bootstrap-ip":   schema.String(),
		NetworkBridgeKey: schema.String(),
		"container":      schema.String(),
		"storage-port":   schema.ForceInt(),
		"namespace":      schema.String(),
	}
	// The port defaults below are not entirely arbitrary.  Local user web
	// frameworks often use 8000 or 8080, so I didn't want to use either of
	// these, but did want the familiarity of using something in the 8000
	// range.
	configDefaults = schema.Defaults{
		"root-dir":       "",
		NetworkBridgeKey: "",
		"container":      string(instance.LXC),
		"bootstrap-ip":   schema.Omit,
		"storage-port":   8040,
		"namespace":      "",
	}
)

type environConfig struct {
	*config.Config
	attrs map[string]interface{}
}

func newEnvironConfig(config *config.Config, attrs map[string]interface{}) *environConfig {
	environConfig := &environConfig{
		Config: config,
		attrs:  attrs,
	}
	// Set correct default network bridge if needed
	// fix for http://pad.lv/1394450
	environConfig.setDefaultNetworkBridge()
	return environConfig
}

// Since it is technically possible for two different users on one machine to
// have the same local provider name, we need to have a simple way to
// namespace the file locations, but more importantly the containers.
func (c *environConfig) namespace() string {
	return c.attrs["namespace"].(string)
}

func (c *environConfig) rootDir() string {
	return c.attrs["root-dir"].(string)
}

func (c *environConfig) container() instance.ContainerType {
	return instance.ContainerType(c.attrs["container"].(string))
}

// setDefaultNetworkBridge sets default network bridge if none is provided.
// Default network bridge varies based on container type.
// Originally, default values were returned in getter. However,
// this meant that older clients were getting incorrect defaults
// (http://pad.lv/1394450)
func (c *environConfig) setDefaultNetworkBridge() {
	name := c.networkBridge()
	switch c.container() {
	case instance.LXC:
		if name == "" {
			name = lxc.DefaultLxcBridge
		}
	case instance.KVM:
		if name == "" || name == lxc.DefaultLxcBridge {
			// Older versions of juju used "lxcbr0" by default,
			// without checking the container type. See also
			// http://pad.lv/1307677.
			name = kvm.DefaultKvmBridge
		}
	}
	c.attrs[NetworkBridgeKey] = name
}

func (c *environConfig) networkBridge() string {
	// We don't care if it's not a string, because Validate takes care
	// of that.
	name, _ := c.attrs[NetworkBridgeKey].(string)
	return name
}

func (c *environConfig) storageDir() string {
	return filepath.Join(c.rootDir(), "storage")
}

func (c *environConfig) mongoDir() string {
	return filepath.Join(c.rootDir(), "db")
}

func (c *environConfig) logDir() string {
	return fmt.Sprintf("%s-%s", agent.DefaultLogDir, c.namespace())
}

// bootstrapIPAddress returns the IP address of the bootstrap machine.
// As of 1.18 this is only set inside the environment, and not in the
// .jenv file.
func (c *environConfig) bootstrapIPAddress() string {
	addr, _ := c.attrs["bootstrap-ip"].(string)
	return addr
}

func (c *environConfig) storagePort() int {
	return c.attrs["storage-port"].(int)
}

func (c *environConfig) storageAddr() string {
	return fmt.Sprintf("%s:%d", c.bootstrapIPAddress(), c.storagePort())
}

func (c *environConfig) configFile(filename string) string {
	return filepath.Join(c.rootDir(), filename)
}

func (c *environConfig) createDirs() error {
	for _, dirname := range []string{
		c.storageDir(),
		c.mongoDir(),
	} {
		logger.Tracef("creating directory %s", dirname)
		if err := os.MkdirAll(dirname, 0755); err != nil {
			return err
		}
	}
	return nil
}
<|endoftext|>


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

package aws

import (
	"context"
	"fmt"
	"log"
	"strings"

	"github.com/GoogleCloudPlatform/terraformer/terraform_utils"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/service/route53"
)

var route53AllowEmptyValues = []string{}

var route53AdditionalFields = map[string]interface{}{}

type Route53Generator struct {
	AWSService
}

func (g Route53Generator) createZonesResources(svc *route53.Client) []terraform_utils.Resource {
	resources := []terraform_utils.Resource{}
	p := route53.NewListHostedZonesPaginator(svc.ListHostedZonesRequest(&route53.ListHostedZonesInput{}))
	for p.Next(context.Background()) {
		for _, zone := range p.CurrentPage().HostedZones {
			zoneID := cleanZoneID(aws.StringValue(zone.Id))
			resources = append(resources, terraform_utils.NewResource(
				zoneID,
				zoneID+"_"+strings.TrimSuffix(aws.StringValue(zone.Name), "."),
				"aws_route53_zone",
				"aws",
				map[string]string{
					"name":          aws.StringValue(zone.Name),
					"force_destroy": "false",
				},
				route53AllowEmptyValues,
				route53AdditionalFields,
			))
			records := g.createRecordsResources(svc, zoneID)
			resources = append(resources, records...)
		}
	}
	if err := p.Err(); err != nil {
		log.Println(err)
	}
	return resources
}

func (Route53Generator) createRecordsResources(svc *route53.Client, zoneID string) []terraform_utils.Resource {
	resources := []terraform_utils.Resource{}
	listParams := &route53.ListResourceRecordSetsInput{
		HostedZoneId: aws.String(zoneID),
	}

	p := route53.NewListResourceRecordSetsPaginator(svc.ListResourceRecordSetsRequest(listParams))
	for p.Next(context.Background()) {
		for _, record := range p.CurrentPage().ResourceRecordSets {
			recordName := wildcardUnescape(aws.StringValue(record.Name))
			typeString, _ := record.Type.MarshalValue()
			resources = append(resources, terraform_utils.NewResource(
				fmt.Sprintf("%s_%s_%s_%s", zoneID, recordName, typeString, aws.StringValue(record.SetIdentifier)),
				fmt.Sprintf("%s_%s_%s_%s", zoneID, recordName, typeString, aws.StringValue(record.SetIdentifier)),
				"aws_route53_record",
				"aws",
				map[string]string{
					"name":           recordName,
					"zone_id":        zoneID,
					"type":           typeString,
					"set_identifier": aws.StringValue(record.SetIdentifier),
				},
				route53AllowEmptyValues,
				route53AdditionalFields,
			))
		}
	}
	if err := p.Err(); err != nil {
		log.Println(err)
		return []terraform_utils.Resource{}
	}
	return resources
}

// Generate TerraformResources from AWS API,
// create terraform resource for each zone + each record
func (g *Route53Generator) InitResources() error {
	config, e := g.generateConfig()
	if e != nil {
		return e
	}
	svc := route53.New(config)

	g.Resources = g.createZonesResources(svc)
	return nil
}

func (g *Route53Generator) PostConvertHook() error {
	for i, resourceRecord := range g.Resources {
		if resourceRecord.InstanceInfo.Type == "aws_route53_zone" {
			continue
		}
		item := resourceRecord.Item
		zoneID := item["zone_id"].(string)
		for _, resourceZone := range g.Resources {
			if resourceZone.InstanceInfo.Type != "aws_route53_zone" {
				continue
			}
			if zoneID == resourceZone.InstanceState.ID {
				g.Resources[i].Item["zone_id"] = "${aws_route53_zone." + resourceZone.ResourceName + ".zone_id}"
			}
		}
		if _, aliasExist := resourceRecord.Item["alias"]; aliasExist {
			if _, ttlExist := resourceRecord.Item["ttl"]; ttlExist {
				delete(g.Resources[i].Item, "ttl")
			}
		}
	}
	return nil
}

func wildcardUnescape(s string) string {
	if strings.Contains(s, "\\052") {
		s = strings.Replace(s, "\\052", "*", 1)
	}
	return s
}

// cleanZoneID is used to remove the leading /hostedzone/
func cleanZoneID(ID string) string {
	return cleanPrefix(ID, "/hostedzone/")
}

// cleanPrefix removes a string prefix from an ID
func cleanPrefix(ID, prefix string) string {
	return strings.TrimPrefix(ID, prefix)
}
<commit_msg>#118 fixed Route 53 record name<commit_after>// Copyright 2018 The Terraformer Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package aws

import (
	"context"
	"fmt"
	"log"
	"strings"

	"github.com/GoogleCloudPlatform/terraformer/terraform_utils"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/service/route53"
)

var route53AllowEmptyValues = []string{}

var route53AdditionalFields = map[string]interface{}{}

type Route53Generator struct {
	AWSService
}

func (g Route53Generator) createZonesResources(svc *route53.Client) []terraform_utils.Resource {
	resources := []terraform_utils.Resource{}
	p := route53.NewListHostedZonesPaginator(svc.ListHostedZonesRequest(&route53.ListHostedZonesInput{}))
	for p.Next(context.Background()) {
		for _, zone := range p.CurrentPage().HostedZones {
			zoneID := cleanZoneID(aws.StringValue(zone.Id))
			resources = append(resources, terraform_utils.NewResource(
				zoneID,
				zoneID+"_"+strings.TrimSuffix(aws.StringValue(zone.Name), "."),
				"aws_route53_zone",
				"aws",
				map[string]string{
					"name":          aws.StringValue(zone.Name),
					"force_destroy": "false",
				},
				route53AllowEmptyValues,
				route53AdditionalFields,
			))
			records := g.createRecordsResources(svc, zoneID)
			resources = append(resources, records...)
		}
	}
	if err := p.Err(); err != nil {
		log.Println(err)
	}
	return resources
}

func (Route53Generator) createRecordsResources(svc *route53.Client, zoneID string) []terraform_utils.Resource {
	var resources []terraform_utils.Resource
	listParams := &route53.ListResourceRecordSetsInput{
		HostedZoneId: aws.String(zoneID),
	}

	p := route53.NewListResourceRecordSetsPaginator(svc.ListResourceRecordSetsRequest(listParams))
	for p.Next(context.Background()) {
		for _, record := range p.CurrentPage().ResourceRecordSets {
			recordName := wildcardUnescape(aws.StringValue(record.Name))
			typeString, _ := record.Type.MarshalValue()
			resources = append(resources, terraform_utils.NewResource(
				fmt.Sprintf("%s_%s_%s_%s", zoneID, recordName, typeString, aws.StringValue(record.SetIdentifier)),
				fmt.Sprintf("%s_%s_%s_%s", zoneID, recordName, typeString, aws.StringValue(record.SetIdentifier)),
				"aws_route53_record",
				"aws",
				map[string]string{
					"name":           strings.TrimSuffix(recordName, "."),
					"zone_id":        zoneID,
					"type":           typeString,
					"set_identifier": aws.StringValue(record.SetIdentifier),
				},
				route53AllowEmptyValues,
				route53AdditionalFields,
			))
		}
	}
	if err := p.Err(); err != nil {
		log.Println(err)
		return []terraform_utils.Resource{}
	}
	return resources
}

// Generate TerraformResources from AWS API,
// create terraform resource for each zone + each record
func (g *Route53Generator) InitResources() error {
	config, e := g.generateConfig()
	if e != nil {
		return e
	}
	svc := route53.New(config)

	g.Resources = g.createZonesResources(svc)
	return nil
}

func (g *Route53Generator) PostConvertHook() error {
	for i, resourceRecord := range g.Resources {
		if resourceRecord.InstanceInfo.Type == "aws_route53_zone" {
			continue
		}
		item := resourceRecord.Item
		zoneID := item["zone_id"].(string)
		for _, resourceZone := range g.Resources {
			if resourceZone.InstanceInfo.Type != "aws_route53_zone" {
				continue
			}
			if zoneID == resourceZone.InstanceState.ID {
				g.Resources[i].Item["zone_id"] = "${aws_route53_zone." + resourceZone.ResourceName + ".zone_id}"
			}
		}
		if _, aliasExist := resourceRecord.Item["alias"]; aliasExist {
			if _, ttlExist := resourceRecord.Item["ttl"]; ttlExist {
				delete(g.Resources[i].Item, "ttl")
			}
		}
	}
	return nil
}

func wildcardUnescape(s string) string {
	if strings.Contains(s, "\\052") {
		s = strings.Replace(s, "\\052", "*", 1)
	}
	return s
}

// cleanZoneID is used to remove the leading /hostedzone/
func cleanZoneID(ID string) string {
	return cleanPrefix(ID, "/hostedzone/")
}

// cleanPrefix removes a string prefix from an ID
func cleanPrefix(ID, prefix string) string {
	return strings.TrimPrefix(ID, prefix)
}
<|endoftext|>


<commit_before>package provision

import (
	"fmt"
	"net/http"
	"strings"
	"testing"

	docker "github.com/fsouza/go-dockerclient"
	fake "github.com/fsouza/go-dockerclient/testing"
)

func TestFnClientWrongClient(t *testing.T) {
	defer func() {
		if r := recover(); r == nil {
			t.Errorf("The code did not panic")
		}
	}()
	FnClient("http://localhost:a")
}

func createFakeDockerAPI(requests *[]*http.Request, t *testing.T) *fake.DockerServer {
	// Fake docker api
	server, err := fake.NewServer("127.0.0.1:0", nil, func(r *http.Request) {
		*requests = append(*requests, r)
	})
	if err != nil {
		t.Fatal(err)
	}
	return server
}

func createFakeImage(client *docker.Client) string {
	client.PullImage(docker.PullImageOptions{Repository: "python"}, docker.AuthConfiguration{})
	return "python"
}

func createFakeContainer(client *docker.Client, t *testing.T) *docker.Container {
	image := createFakeImage(client)
	container, err := client.CreateContainer(docker.CreateContainerOptions{
		Name: "gofn-123",
		Config: &docker.Config{
			Image:     image,
			StdinOnce: true,
			OpenStdin: true,
		},
	})
	if err != nil {
		t.Fatal(err)
	}
	return container
}

func NewTestClient(host string, t *testing.T) *docker.Client {
	client, err := docker.NewClient(host)
	if err != nil {
		t.Fatal(err)
	}
	return client
}

func TestFnRemoveContainerSuccessfully(t *testing.T) {

	var requests []*http.Request
	server := createFakeDockerAPI(&requests, t)
	defer server.Stop()

	// Instantiate a client
	client := NewTestClient(server.URL(), t)

	// Create container before remove it
	container := createFakeContainer(client, t)

	// Remove a container
	if e := FnRemove(client, container.ID); e != nil {
		t.Errorf("Expected no errors but %q found", e)
	}

	// last request
	lastRequest := requests[len(requests)-1]
	if lastRequest.Method != http.MethodDelete {
		t.Errorf("expected method DELETE but %q found", lastRequest.Method)
	}
	if lastRequest.URL.Path != fmt.Sprintf("/containers/%s", container.ID) {
		t.Errorf("expected \"containers/%s\" but path was %q", container.ID, lastRequest.URL.Path)
	}

}

func TestFnRemoveContainerImageNotFound(t *testing.T) {

	var requests []*http.Request
	server := createFakeDockerAPI(&requests, t)
	defer server.Stop()

	// Instantiate a client
	client := NewTestClient(server.URL(), t)

	// Remove a container
	if e := FnRemove(client, "wrong"); e == nil {
		t.Error("expected errors but no errors found")
	}

	// last request
	lastRequest := requests[len(requests)-1]
	if lastRequest.Method != http.MethodDelete {
		t.Errorf("expected method DELETE but %q found", lastRequest.Method)
	}
	if lastRequest.URL.Path != fmt.Sprintf("/containers/%s", "wrong") {
		t.Errorf("expected \"containers/%s\" but path was %q", "wrong", lastRequest.URL.Path)
	}
}

func TestFnContainerCreatedSuccessfully(t *testing.T) {

	var requests []*http.Request
	server := createFakeDockerAPI(&requests, t)
	defer server.Stop()

	// Instantiate a client
	client := NewTestClient(server.URL(), t)
	image := createFakeImage(client)

	container, err := FnContainer(client, image, "")
	if err != nil {
		t.Errorf("Expected no errors but %q found", err)
	}
	// container name should starts with gofn
	if !strings.HasPrefix(container.Name, "gofn") {
		t.Errorf("container should starts with gofn but found %q", container.Name)
	}
	lastRequest := requests[len(requests)-1]
	if lastRequest.Method != http.MethodPost {
		t.Errorf("expected method %q but %q found", http.MethodPost, lastRequest.Method)
	}
	if !strings.Contains(lastRequest.URL.Path, "/containers/create") {
		t.Errorf("Path is not ok, found: %q", lastRequest.URL.Path)
	}

}

func TestFnContainerInvalidImage(t *testing.T) {

	var requests []*http.Request
	server := createFakeDockerAPI(&requests, t)
	defer server.Stop()

	// Instantiate a client
	client := NewTestClient(server.URL(), t)
	image := "wrong"

	_, err := FnContainer(client, image, "")
	if err == nil {
		t.Errorf("Expected errors but no errors found")
	}

	lastRequest := requests[len(requests)-1]
	if lastRequest.Method != http.MethodPost {
		t.Errorf("expected method %q but %q found", http.MethodPost, lastRequest.Method)
	}
	if !strings.Contains(lastRequest.URL.Path, "/containers/create") {
		t.Errorf("Path is not ok, found: %q", lastRequest.URL.Path)
	}

}

func TestFnContainerCreatedWithVolume(t *testing.T) {

	var requests []*http.Request
	server := createFakeDockerAPI(&requests, t)
	defer server.Stop()

	// Instantiate a client
	client := NewTestClient(server.URL(), t)
	image := createFakeImage(client)

	volume := "/tmp:/tmp"
	container, err := FnContainer(client, image, volume)
	if err != nil {
		t.Errorf("Expected no errors but %q found", err)
	}
	// container name should starts with gofn
	if !strings.HasPrefix(container.Name, "gofn") {
		t.Errorf("container should starts with gofn but found %q", container.Name)
	}
	// volume is binded with container
	if container.HostConfig.Binds[0] != volume {
		t.Errorf("expected volume %q bout found %q", volume, container.HostConfig.Binds[0])
	}
	lastRequest := requests[len(requests)-1]
	if lastRequest.Method != http.MethodPost {
		t.Errorf("expected method %q but %q found", http.MethodPost, lastRequest.Method)
	}
	if !strings.Contains(lastRequest.URL.Path, "/containers/create") {
		t.Errorf("Path is not ok, found: %q", lastRequest.URL.Path)
	}

}
func TestFnBuildImageSuccessfully(t *testing.T) {
	var requests []*http.Request
	server := createFakeDockerAPI(&requests, t)
	defer server.Stop()

	// Instantiate a client
	client := NewTestClient(server.URL(), t)
	name, _ := FnImageBuild(client, "./testing_data", "", "test")

	imageName := "gofn/test"
	if name != imageName {
		t.Errorf("image name expected %q but found %q", imageName, name)
	}

	lastRequest := requests[len(requests)-1]
	if lastRequest.Method != http.MethodPost {
		t.Errorf("expected method %q but %q found", http.MethodPost, lastRequest.Method)
	}
	if !strings.Contains(lastRequest.URL.Path, "/build") {
		t.Errorf("Path is not ok, found: %q", lastRequest.URL.Path)
	}
}

func TestFnBuildImageDockerfileNotFound(t *testing.T) {
	var requests []*http.Request
	server := createFakeDockerAPI(&requests, t)
	defer server.Stop()

	// Instantiate a client
	client := NewTestClient(server.URL(), t)
	defer func() {
		if r := recover(); r == nil {
			t.Errorf("the code did not panic")
		}
	}()
	FnImageBuild(client, "./wrong", "Dockerfile", "test")
}

func TestFnFindImageSuccessfully(t *testing.T) {
	var requests []*http.Request
	server := createFakeDockerAPI(&requests, t)
	defer server.Stop()

	// Instantiate a client
	client := NewTestClient(server.URL(), t)
	img := createFakeImage(client)

	_, err := FnFindImage(client, img)
	if err != nil {
		t.Errorf("no errors expected but found %q", err)
	}
	lastRequest := requests[len(requests)-1]
	if lastRequest.Method != http.MethodGet {
		t.Errorf("expected method %q but %q found", http.MethodGet, lastRequest.Method)
	}
	if !strings.Contains(lastRequest.URL.Path, "/images/json") {
		t.Errorf("Path is not ok, found: %q", lastRequest.URL.Path)
	}
}

func TestFnFindImageImageNotFound(t *testing.T) {
	var requests []*http.Request
	server := createFakeDockerAPI(&requests, t)
	defer server.Stop()

	// Instantiate a client
	client := NewTestClient(server.URL(), t)

	_, err := FnFindImage(client, "wrong")
	if err != ErrImageNotFound {
		t.Errorf("unexpected error: %q", err)
	}
	lastRequest := requests[len(requests)-1]
	if lastRequest.Method != http.MethodGet {
		t.Errorf("expected method %q but %q found", http.MethodGet, lastRequest.Method)
	}
	if !strings.Contains(lastRequest.URL.Path, "/images/json") {
		t.Errorf("Path is not ok, found: %q", lastRequest.URL.Path)
	}
}

func TestFnFindImageServerError(t *testing.T) {
	client := NewTestClient("wrong", t)

	_, err := FnFindImage(client, "wrong")
	if err == nil || err == ErrImageNotFound {
		t.Errorf("Expected other errors but found ImageNotfound or null: %q", err)
	}
}

func TestFnConfigVolumeAllEmpty(t *testing.T) {
	volume := FnConfigVolume(&VolumeOptions{})
	if volume != "" {
		t.Errorf("Expected \"\" but found %q", volume)
	}
}

func TestFnConfigVolumeDestinationOmitted(t *testing.T) {
	volume := FnConfigVolume(&VolumeOptions{Source: "/tmp"})
	if volume != "/tmp:/tmp" {
		t.Errorf("Expected \"/tmp:/tmp\" but found %q", volume)
	}
}

func TestFnConfigVolumeOnlyDestination(t *testing.T) {
	volume := FnConfigVolume(&VolumeOptions{Destination: "/tmp"})
	if volume != ":/tmp" {
		t.Errorf("Expected \":/tmp\" but found %q", volume)
	}
}

func TestFnConfigVolumeAllFields(t *testing.T) {
	volume := FnConfigVolume(&VolumeOptions{
		Source:      "/tmp",
		Destination: "/tmp",
	})
	if volume != "/tmp:/tmp" {
		t.Errorf("Expected \"/tmp:/tmp\" but found %q", volume)
	}
}
<commit_msg>added FnFindContainers tests<commit_after>package provision

import (
	"fmt"
	"net/http"
	"strings"
	"testing"

	docker "github.com/fsouza/go-dockerclient"
	fake "github.com/fsouza/go-dockerclient/testing"
)

func TestFnClientWrongClient(t *testing.T) {
	defer func() {
		if r := recover(); r == nil {
			t.Errorf("The code did not panic")
		}
	}()
	FnClient("http://localhost:a")
}

func createFakeDockerAPI(requests *[]*http.Request, t *testing.T) *fake.DockerServer {
	// Fake docker api
	server, err := fake.NewServer("127.0.0.1:0", nil, func(r *http.Request) {
		*requests = append(*requests, r)
	})
	if err != nil {
		t.Fatal(err)
	}
	return server
}

func createFakeImage(client *docker.Client) string {
	client.PullImage(docker.PullImageOptions{Repository: "gofn/python"}, docker.AuthConfiguration{})
	return "gofn/python"
}

func createFakeContainer(client *docker.Client, t *testing.T) *docker.Container {
	image := createFakeImage(client)
	container, err := client.CreateContainer(docker.CreateContainerOptions{
		Name: "gofn-123",
		Config: &docker.Config{
			Image:     image,
			StdinOnce: true,
			OpenStdin: true,
		},
	})
	if err != nil {
		t.Fatal(err)
	}
	return container
}

func NewTestClient(host string, t *testing.T) *docker.Client {
	client, err := docker.NewClient(host)
	if err != nil {
		t.Fatal(err)
	}
	return client
}

func TestFnRemoveContainerSuccessfully(t *testing.T) {

	var requests []*http.Request
	server := createFakeDockerAPI(&requests, t)
	defer server.Stop()

	// Instantiate a client
	client := NewTestClient(server.URL(), t)

	// Create container before remove it
	container := createFakeContainer(client, t)

	// Remove a container
	if e := FnRemove(client, container.ID); e != nil {
		t.Errorf("Expected no errors but %q found", e)
	}

	// last request
	lastRequest := requests[len(requests)-1]
	if lastRequest.Method != http.MethodDelete {
		t.Errorf("expected method DELETE but %q found", lastRequest.Method)
	}
	if lastRequest.URL.Path != fmt.Sprintf("/containers/%s", container.ID) {
		t.Errorf("expected \"containers/%s\" but path was %q", container.ID, lastRequest.URL.Path)
	}

}

func TestFnRemoveContainerImageNotFound(t *testing.T) {

	var requests []*http.Request
	server := createFakeDockerAPI(&requests, t)
	defer server.Stop()

	// Instantiate a client
	client := NewTestClient(server.URL(), t)

	// Remove a container
	if e := FnRemove(client, "wrong"); e == nil {
		t.Error("expected errors but no errors found")
	}

	// last request
	lastRequest := requests[len(requests)-1]
	if lastRequest.Method != http.MethodDelete {
		t.Errorf("expected method DELETE but %q found", lastRequest.Method)
	}
	if lastRequest.URL.Path != fmt.Sprintf("/containers/%s", "wrong") {
		t.Errorf("expected \"containers/%s\" but path was %q", "wrong", lastRequest.URL.Path)
	}
}

func TestFnContainerCreatedSuccessfully(t *testing.T) {

	var requests []*http.Request
	server := createFakeDockerAPI(&requests, t)
	defer server.Stop()

	// Instantiate a client
	client := NewTestClient(server.URL(), t)
	image := createFakeImage(client)

	container, err := FnContainer(client, image, "")
	if err != nil {
		t.Errorf("Expected no errors but %q found", err)
	}
	// container name should starts with gofn
	if !strings.HasPrefix(container.Name, "gofn") {
		t.Errorf("container should starts with gofn but found %q", container.Name)
	}
	lastRequest := requests[len(requests)-1]
	if lastRequest.Method != http.MethodPost {
		t.Errorf("expected method %q but %q found", http.MethodPost, lastRequest.Method)
	}
	if !strings.Contains(lastRequest.URL.Path, "/containers/create") {
		t.Errorf("Path is not ok, found: %q", lastRequest.URL.Path)
	}

}

func TestFnContainerInvalidImage(t *testing.T) {

	var requests []*http.Request
	server := createFakeDockerAPI(&requests, t)
	defer server.Stop()

	// Instantiate a client
	client := NewTestClient(server.URL(), t)
	image := "wrong"

	_, err := FnContainer(client, image, "")
	if err == nil {
		t.Errorf("Expected errors but no errors found")
	}

	lastRequest := requests[len(requests)-1]
	if lastRequest.Method != http.MethodPost {
		t.Errorf("expected method %q but %q found", http.MethodPost, lastRequest.Method)
	}
	if !strings.Contains(lastRequest.URL.Path, "/containers/create") {
		t.Errorf("Path is not ok, found: %q", lastRequest.URL.Path)
	}

}

func TestFnContainerCreatedWithVolume(t *testing.T) {

	var requests []*http.Request
	server := createFakeDockerAPI(&requests, t)
	defer server.Stop()

	// Instantiate a client
	client := NewTestClient(server.URL(), t)
	image := createFakeImage(client)

	volume := "/tmp:/tmp"
	container, err := FnContainer(client, image, volume)
	if err != nil {
		t.Errorf("Expected no errors but %q found", err)
	}
	// container name should starts with gofn
	if !strings.HasPrefix(container.Name, "gofn") {
		t.Errorf("container should starts with gofn but found %q", container.Name)
	}
	// volume is binded with container
	if container.HostConfig.Binds[0] != volume {
		t.Errorf("expected volume %q bout found %q", volume, container.HostConfig.Binds[0])
	}
	lastRequest := requests[len(requests)-1]
	if lastRequest.Method != http.MethodPost {
		t.Errorf("expected method %q but %q found", http.MethodPost, lastRequest.Method)
	}
	if !strings.Contains(lastRequest.URL.Path, "/containers/create") {
		t.Errorf("Path is not ok, found: %q", lastRequest.URL.Path)
	}

}
func TestFnBuildImageSuccessfully(t *testing.T) {
	var requests []*http.Request
	server := createFakeDockerAPI(&requests, t)
	defer server.Stop()

	// Instantiate a client
	client := NewTestClient(server.URL(), t)
	name, _ := FnImageBuild(client, "./testing_data", "", "test")

	imageName := "gofn/test"
	if name != imageName {
		t.Errorf("image name expected %q but found %q", imageName, name)
	}

	lastRequest := requests[len(requests)-1]
	if lastRequest.Method != http.MethodPost {
		t.Errorf("expected method %q but %q found", http.MethodPost, lastRequest.Method)
	}
	if !strings.Contains(lastRequest.URL.Path, "/build") {
		t.Errorf("Path is not ok, found: %q", lastRequest.URL.Path)
	}
}

func TestFnBuildImageDockerfileNotFound(t *testing.T) {
	var requests []*http.Request
	server := createFakeDockerAPI(&requests, t)
	defer server.Stop()

	// Instantiate a client
	client := NewTestClient(server.URL(), t)
	defer func() {
		if r := recover(); r == nil {
			t.Errorf("the code did not panic")
		}
	}()
	FnImageBuild(client, "./wrong", "Dockerfile", "test")
}

func TestFnFindImageSuccessfully(t *testing.T) {
	var requests []*http.Request
	server := createFakeDockerAPI(&requests, t)
	defer server.Stop()

	// Instantiate a client
	client := NewTestClient(server.URL(), t)
	img := createFakeImage(client)

	_, err := FnFindImage(client, img)
	if err != nil {
		t.Errorf("no errors expected but found %q", err)
	}
	lastRequest := requests[len(requests)-1]
	if lastRequest.Method != http.MethodGet {
		t.Errorf("expected method %q but %q found", http.MethodGet, lastRequest.Method)
	}
	if !strings.Contains(lastRequest.URL.Path, "/images/json") {
		t.Errorf("Path is not ok, found: %q", lastRequest.URL.Path)
	}
}

func TestFnFindImageImageNotFound(t *testing.T) {
	var requests []*http.Request
	server := createFakeDockerAPI(&requests, t)
	defer server.Stop()

	// Instantiate a client
	client := NewTestClient(server.URL(), t)

	_, err := FnFindImage(client, "wrong")
	if err != ErrImageNotFound {
		t.Errorf("unexpected error: %q", err)
	}
	lastRequest := requests[len(requests)-1]
	if lastRequest.Method != http.MethodGet {
		t.Errorf("expected method %q but %q found", http.MethodGet, lastRequest.Method)
	}
	if !strings.Contains(lastRequest.URL.Path, "/images/json") {
		t.Errorf("Path is not ok, found: %q", lastRequest.URL.Path)
	}
}

func TestFnFindImageServerError(t *testing.T) {
	client := NewTestClient("wrong", t)

	_, err := FnFindImage(client, "wrong")
	if err == nil || err == ErrImageNotFound {
		t.Errorf("Expected other errors but found ImageNotfound or null: %q", err)
	}
}

func TestFnFindContainerSuccessfully(t *testing.T) {
	var requests []*http.Request
	server := createFakeDockerAPI(&requests, t)
	defer server.Stop()

	// Instantiate a client
	client := NewTestClient(server.URL(), t)

	// Create container before find it
	container := createFakeContainer(client, t)

	// Find a container by image
	if _, e := FnFindContainer(client, container.Image); e != nil {
		t.Errorf("Expected no errors but %q found", e)
	}

	// last request
	lastRequest := requests[len(requests)-1]
	if lastRequest.Method != http.MethodGet {
		t.Errorf("expected %q  but %q found", http.MethodGet, lastRequest.Method)
	}
	if lastRequest.URL.Path != "/containers/json" {
		t.Errorf("expected \"containers/json\" but path was %q", lastRequest.URL.Path)
	}
}

func TestFnFindContainerContainerNotFound(t *testing.T) {
	var requests []*http.Request
	server := createFakeDockerAPI(&requests, t)
	defer server.Stop()

	// Instantiate a client
	client := NewTestClient(server.URL(), t)

	// Find a container by image
	if _, e := FnFindContainer(client, "python"); e != ErrContainerNotFound {
		t.Errorf("Expected %q but found %q", ErrContainerNotFound, e)
	}

	// last request
	lastRequest := requests[len(requests)-1]
	if lastRequest.Method != http.MethodGet {
		t.Errorf("expected %q  but %q found", http.MethodGet, lastRequest.Method)
	}
	if lastRequest.URL.Path != "/containers/json" {
		t.Errorf("expected \"containers/json\" but path was %q", lastRequest.URL.Path)
	}
}

func TestFnFindContainerServerError(t *testing.T) {
	client := NewTestClient("wrong", t)

	_, err := FnFindContainer(client, "wrong")
	if err == nil || err == ErrContainerNotFound {
		t.Errorf("Expected other errors but found COntainerNotfound or null: %q", err)
	}
}
func TestFnConfigVolumeAllEmpty(t *testing.T) {
	volume := FnConfigVolume(&VolumeOptions{})
	if volume != "" {
		t.Errorf("Expected \"\" but found %q", volume)
	}
}

func TestFnConfigVolumeDestinationOmitted(t *testing.T) {
	volume := FnConfigVolume(&VolumeOptions{Source: "/tmp"})
	if volume != "/tmp:/tmp" {
		t.Errorf("Expected \"/tmp:/tmp\" but found %q", volume)
	}
}

func TestFnConfigVolumeOnlyDestination(t *testing.T) {
	volume := FnConfigVolume(&VolumeOptions{Destination: "/tmp"})
	if volume != ":/tmp" {
		t.Errorf("Expected \":/tmp\" but found %q", volume)
	}
}

func TestFnConfigVolumeAllFields(t *testing.T) {
	volume := FnConfigVolume(&VolumeOptions{
		Source:      "/tmp",
		Destination: "/tmp",
	})
	if volume != "/tmp:/tmp" {
		t.Errorf("Expected \"/tmp:/tmp\" but found %q", volume)
	}
}
<|endoftext|>


<commit_before>package freedom

import (
	"net"
	"sync"

	"github.com/v2ray/v2ray-core"
	"github.com/v2ray/v2ray-core/common/log"
	v2net "github.com/v2ray/v2ray-core/common/net"
)

type FreedomConnection struct {
	packet v2net.Packet
}

func NewFreedomConnection(firstPacket v2net.Packet) *FreedomConnection {
	return &FreedomConnection{
		packet: firstPacket,
	}
}

func (vconn *FreedomConnection) Start(ray core.OutboundRay) error {
	conn, err := net.Dial(vconn.packet.Destination().Network(), vconn.packet.Destination().Address().String())
	log.Info("Freedom: Opening connection to %s", vconn.packet.Destination().String())
	if err != nil {
		if ray != nil {
			close(ray.OutboundOutput())
		}
		return log.Error("Freedom: Failed to open connection: %s : %v", vconn.packet.Destination().String(), err)
	}

	input := ray.OutboundInput()
	output := ray.OutboundOutput()
	var readMutex, writeMutex sync.Mutex
	readMutex.Lock()
	writeMutex.Lock()

	if chunk := vconn.packet.Chunk(); chunk != nil {
		conn.Write(chunk)
	}

	if !vconn.packet.MoreChunks() {
		writeMutex.Unlock()
	} else {
		go dumpInput(conn, input, &writeMutex)
	}

	go dumpOutput(conn, output, &readMutex, vconn.packet.Destination().IsUDP())

	go func() {
		writeMutex.Lock()
		if tcpConn, ok := conn.(*net.TCPConn); ok {
			tcpConn.CloseWrite()
		}
		readMutex.Lock()
		conn.Close()
	}()

	return nil
}

func dumpInput(conn net.Conn, input <-chan []byte, finish *sync.Mutex) {
	v2net.ChanToWriter(conn, input)
	finish.Unlock()
}

func dumpOutput(conn net.Conn, output chan<- []byte, finish *sync.Mutex, udp bool) {
	defer finish.Unlock()
	defer close(output)

	response, err := v2net.ReadFrom(conn)
	if len(response) > 0 {
		output <- response
	}
	if err != nil {
		return
	}
	if udp {
		return
	}

	v2net.ReaderToChan(output, conn)
}
<commit_msg>More logs<commit_after>package freedom

import (
	"net"
	"sync"

	"github.com/v2ray/v2ray-core"
	"github.com/v2ray/v2ray-core/common/log"
	v2net "github.com/v2ray/v2ray-core/common/net"
)

type FreedomConnection struct {
	packet v2net.Packet
}

func NewFreedomConnection(firstPacket v2net.Packet) *FreedomConnection {
	return &FreedomConnection{
		packet: firstPacket,
	}
}

func (vconn *FreedomConnection) Start(ray core.OutboundRay) error {
	conn, err := net.Dial(vconn.packet.Destination().Network(), vconn.packet.Destination().Address().String())
	log.Info("Freedom: Opening connection to %s", vconn.packet.Destination().String())
	if err != nil {
		if ray != nil {
			close(ray.OutboundOutput())
		}
		return log.Error("Freedom: Failed to open connection: %s : %v", vconn.packet.Destination().String(), err)
	}

	input := ray.OutboundInput()
	output := ray.OutboundOutput()
	var readMutex, writeMutex sync.Mutex
	readMutex.Lock()
	writeMutex.Lock()

	if chunk := vconn.packet.Chunk(); chunk != nil {
		conn.Write(chunk)
	}

	if !vconn.packet.MoreChunks() {
		writeMutex.Unlock()
	} else {
		go dumpInput(conn, input, &writeMutex)
	}

	go dumpOutput(conn, output, &readMutex, vconn.packet.Destination().IsUDP())

	go func() {
		writeMutex.Lock()
		if tcpConn, ok := conn.(*net.TCPConn); ok {
			tcpConn.CloseWrite()
		}
		readMutex.Lock()
		conn.Close()
	}()

	return nil
}

func dumpInput(conn net.Conn, input <-chan []byte, finish *sync.Mutex) {
	v2net.ChanToWriter(conn, input)
	finish.Unlock()
}

func dumpOutput(conn net.Conn, output chan<- []byte, finish *sync.Mutex, udp bool) {
	defer finish.Unlock()
	defer close(output)

	response, err := v2net.ReadFrom(conn)
  log.Info("Freedom receives %d bytes from %s", len(response), conn.RemoteAddr().String())
	if len(response) > 0 {
		output <- response
	}
	if err != nil {
		return
	}
	if udp {
		return
	}

	v2net.ReaderToChan(output, conn)
}
<|endoftext|>


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

package grpcproxy

import (
	"io"
	"sync"
	"sync/atomic"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"

	"golang.org/x/net/context"

	"github.com/coreos/etcd/clientv3"
	"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
)

type leaseProxy struct {
	// leaseClient handles req from LeaseGrant() that requires a lease ID.
	leaseClient pb.LeaseClient

	lessor clientv3.Lease

	ctx context.Context

	leader *leader

	// mu protects adding outstanding leaseProxyStream through wg.
	mu sync.RWMutex

	// wg waits until all outstanding leaseProxyStream quit.
	wg sync.WaitGroup
}

func NewLeaseProxy(c *clientv3.Client) (pb.LeaseServer, <-chan struct{}) {
	cctx, cancel := context.WithCancel(c.Ctx())
	lp := &leaseProxy{
		leaseClient: pb.NewLeaseClient(c.ActiveConnection()),
		lessor:      c.Lease,
		ctx:         cctx,
		leader:      newLeader(c.Ctx(), c.Watcher),
	}
	ch := make(chan struct{})
	go func() {
		defer close(ch)
		<-lp.leader.stopNotify()
		lp.mu.Lock()
		select {
		case <-lp.ctx.Done():
		case <-lp.leader.disconnectNotify():
			cancel()
		}
		<-lp.ctx.Done()
		lp.mu.Unlock()
		lp.wg.Wait()
	}()
	return lp, ch
}

func (lp *leaseProxy) LeaseGrant(ctx context.Context, cr *pb.LeaseGrantRequest) (*pb.LeaseGrantResponse, error) {
	rp, err := lp.leaseClient.LeaseGrant(ctx, cr)
	if err != nil {
		return nil, err
	}
	lp.leader.gotLeader()
	return rp, nil
}

func (lp *leaseProxy) LeaseRevoke(ctx context.Context, rr *pb.LeaseRevokeRequest) (*pb.LeaseRevokeResponse, error) {
	r, err := lp.lessor.Revoke(ctx, clientv3.LeaseID(rr.ID))
	if err != nil {
		return nil, err
	}
	lp.leader.gotLeader()
	return (*pb.LeaseRevokeResponse)(r), nil
}

func (lp *leaseProxy) LeaseTimeToLive(ctx context.Context, rr *pb.LeaseTimeToLiveRequest) (*pb.LeaseTimeToLiveResponse, error) {
	var (
		r   *clientv3.LeaseTimeToLiveResponse
		err error
	)
	if rr.Keys {
		r, err = lp.lessor.TimeToLive(ctx, clientv3.LeaseID(rr.ID), clientv3.WithAttachedKeys())
	} else {
		r, err = lp.lessor.TimeToLive(ctx, clientv3.LeaseID(rr.ID))
	}
	if err != nil {
		return nil, err
	}
	rp := &pb.LeaseTimeToLiveResponse{
		Header:     r.ResponseHeader,
		ID:         int64(r.ID),
		TTL:        r.TTL,
		GrantedTTL: r.GrantedTTL,
		Keys:       r.Keys,
	}
	return rp, err
}

func (lp *leaseProxy) LeaseKeepAlive(stream pb.Lease_LeaseKeepAliveServer) error {
	lp.mu.Lock()
	select {
	case <-lp.ctx.Done():
		lp.mu.Unlock()
		return lp.ctx.Err()
	default:
		lp.wg.Add(1)
	}
	lp.mu.Unlock()

	ctx, cancel := context.WithCancel(stream.Context())
	lps := leaseProxyStream{
		stream:          stream,
		lessor:          lp.lessor,
		keepAliveLeases: make(map[int64]*atomicCounter),
		respc:           make(chan *pb.LeaseKeepAliveResponse),
		ctx:             ctx,
		cancel:          cancel,
	}

	errc := make(chan error, 2)

	var lostLeaderC <-chan struct{}
	if md, ok := metadata.FromOutgoingContext(stream.Context()); ok {
		v := md[rpctypes.MetadataRequireLeaderKey]
		if len(v) > 0 && v[0] == rpctypes.MetadataHasLeader {
			lostLeaderC = lp.leader.lostNotify()
			// if leader is known to be lost at creation time, avoid
			// letting events through at all
			select {
			case <-lostLeaderC:
				lp.wg.Done()
				return rpctypes.ErrNoLeader
			default:
			}
		}
	}
	stopc := make(chan struct{}, 3)
	go func() {
		defer func() { stopc <- struct{}{} }()
		if err := lps.recvLoop(); err != nil {
			errc <- err
		}
	}()

	go func() {
		defer func() { stopc <- struct{}{} }()
		if err := lps.sendLoop(); err != nil {
			errc <- err
		}
	}()

	// tears down LeaseKeepAlive stream if leader goes down or entire leaseProxy is terminated.
	go func() {
		defer func() { stopc <- struct{}{} }()
		select {
		case <-lostLeaderC:
		case <-ctx.Done():
		case <-lp.ctx.Done():
		}
	}()

	var err error
	select {
	case <-stopc:
		stopc <- struct{}{}
	case err = <-errc:
	}
	cancel()

	// recv/send may only shutdown after function exits;
	// this goroutine notifies lease proxy that the stream is through
	go func() {
		<-stopc
		<-stopc
		<-stopc
		lps.close()
		close(errc)
		lp.wg.Done()
	}()

	select {
	case <-lostLeaderC:
		return rpctypes.ErrNoLeader
	case <-lp.leader.disconnectNotify():
		return grpc.ErrClientConnClosing
	default:
		if err != nil {
			return err
		}
		return ctx.Err()
	}
}

type leaseProxyStream struct {
	stream pb.Lease_LeaseKeepAliveServer

	lessor clientv3.Lease
	// wg tracks keepAliveLoop goroutines
	wg sync.WaitGroup
	// mu protects keepAliveLeases
	mu sync.RWMutex
	// keepAliveLeases tracks how many outstanding keepalive requests which need responses are on a lease.
	keepAliveLeases map[int64]*atomicCounter
	// respc receives lease keepalive responses from etcd backend
	respc chan *pb.LeaseKeepAliveResponse

	ctx    context.Context
	cancel context.CancelFunc
}

func (lps *leaseProxyStream) recvLoop() error {
	for {
		rr, err := lps.stream.Recv()
		if err == io.EOF {
			return nil
		}
		if err != nil {
			return err
		}
		lps.mu.Lock()
		neededResps, ok := lps.keepAliveLeases[rr.ID]
		if !ok {
			neededResps = &atomicCounter{}
			lps.keepAliveLeases[rr.ID] = neededResps
			lps.wg.Add(1)
			go func() {
				defer lps.wg.Done()
				if err := lps.keepAliveLoop(rr.ID, neededResps); err != nil {
					lps.cancel()
				}
			}()
		}
		neededResps.add(1)
		lps.mu.Unlock()
	}
}

func (lps *leaseProxyStream) keepAliveLoop(leaseID int64, neededResps *atomicCounter) error {
	cctx, ccancel := context.WithCancel(lps.ctx)
	defer ccancel()
	respc, err := lps.lessor.KeepAlive(cctx, clientv3.LeaseID(leaseID))
	if err != nil {
		return err
	}
	// ticker expires when loop hasn't received keepalive within TTL
	var ticker <-chan time.Time
	for {
		select {
		case <-ticker:
			lps.mu.Lock()
			// if there are outstanding keepAlive reqs at the moment of ticker firing,
			// don't close keepAliveLoop(), let it continuing to process the KeepAlive reqs.
			if neededResps.get() > 0 {
				lps.mu.Unlock()
				ticker = nil
				continue
			}
			delete(lps.keepAliveLeases, leaseID)
			lps.mu.Unlock()
			return nil
		case rp, ok := <-respc:
			if !ok {
				lps.mu.Lock()
				delete(lps.keepAliveLeases, leaseID)
				lps.mu.Unlock()
				if neededResps.get() == 0 {
					return nil
				}
				ttlResp, err := lps.lessor.TimeToLive(cctx, clientv3.LeaseID(leaseID))
				if err != nil {
					return err
				}
				r := &pb.LeaseKeepAliveResponse{
					Header: ttlResp.ResponseHeader,
					ID:     int64(ttlResp.ID),
					TTL:    ttlResp.TTL,
				}
				for neededResps.get() > 0 {
					select {
					case lps.respc <- r:
						neededResps.add(-1)
					case <-lps.ctx.Done():
						return nil
					}
				}
				return nil
			}
			if neededResps.get() == 0 {
				continue
			}
			ticker = time.After(time.Duration(rp.TTL) * time.Second)
			r := &pb.LeaseKeepAliveResponse{
				Header: rp.ResponseHeader,
				ID:     int64(rp.ID),
				TTL:    rp.TTL,
			}
			lps.replyToClient(r, neededResps)
		}
	}
}

func (lps *leaseProxyStream) replyToClient(r *pb.LeaseKeepAliveResponse, neededResps *atomicCounter) {
	timer := time.After(500 * time.Millisecond)
	for neededResps.get() > 0 {
		select {
		case lps.respc <- r:
			neededResps.add(-1)
		case <-timer:
			return
		case <-lps.ctx.Done():
			return
		}
	}
}

func (lps *leaseProxyStream) sendLoop() error {
	for {
		select {
		case lrp, ok := <-lps.respc:
			if !ok {
				return nil
			}
			if err := lps.stream.Send(lrp); err != nil {
				return err
			}
		case <-lps.ctx.Done():
			return lps.ctx.Err()
		}
	}
}

func (lps *leaseProxyStream) close() {
	lps.cancel()
	lps.wg.Wait()
	// only close respc channel if all the keepAliveLoop() goroutines have finished
	// this ensures those goroutines don't send resp to a closed resp channel
	close(lps.respc)
}

type atomicCounter struct {
	counter int64
}

func (ac *atomicCounter) add(delta int64) {
	atomic.AddInt64(&ac.counter, delta)
}

func (ac *atomicCounter) get() int64 {
	return atomic.LoadInt64(&ac.counter)
}
<commit_msg>grpcproxy: Disable fast fail on lease grant call to cluster<commit_after>// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package grpcproxy

import (
	"io"
	"sync"
	"sync/atomic"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"

	"golang.org/x/net/context"

	"github.com/coreos/etcd/clientv3"
	"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
)

type leaseProxy struct {
	// leaseClient handles req from LeaseGrant() that requires a lease ID.
	leaseClient pb.LeaseClient

	lessor clientv3.Lease

	ctx context.Context

	leader *leader

	// mu protects adding outstanding leaseProxyStream through wg.
	mu sync.RWMutex

	// wg waits until all outstanding leaseProxyStream quit.
	wg sync.WaitGroup
}

func NewLeaseProxy(c *clientv3.Client) (pb.LeaseServer, <-chan struct{}) {
	cctx, cancel := context.WithCancel(c.Ctx())
	lp := &leaseProxy{
		leaseClient: pb.NewLeaseClient(c.ActiveConnection()),
		lessor:      c.Lease,
		ctx:         cctx,
		leader:      newLeader(c.Ctx(), c.Watcher),
	}
	ch := make(chan struct{})
	go func() {
		defer close(ch)
		<-lp.leader.stopNotify()
		lp.mu.Lock()
		select {
		case <-lp.ctx.Done():
		case <-lp.leader.disconnectNotify():
			cancel()
		}
		<-lp.ctx.Done()
		lp.mu.Unlock()
		lp.wg.Wait()
	}()
	return lp, ch
}

func (lp *leaseProxy) LeaseGrant(ctx context.Context, cr *pb.LeaseGrantRequest) (*pb.LeaseGrantResponse, error) {
	rp, err := lp.leaseClient.LeaseGrant(ctx, cr, grpc.FailFast(false))
	if err != nil {
		return nil, err
	}
	lp.leader.gotLeader()
	return rp, nil
}

func (lp *leaseProxy) LeaseRevoke(ctx context.Context, rr *pb.LeaseRevokeRequest) (*pb.LeaseRevokeResponse, error) {
	r, err := lp.lessor.Revoke(ctx, clientv3.LeaseID(rr.ID))
	if err != nil {
		return nil, err
	}
	lp.leader.gotLeader()
	return (*pb.LeaseRevokeResponse)(r), nil
}

func (lp *leaseProxy) LeaseTimeToLive(ctx context.Context, rr *pb.LeaseTimeToLiveRequest) (*pb.LeaseTimeToLiveResponse, error) {
	var (
		r   *clientv3.LeaseTimeToLiveResponse
		err error
	)
	if rr.Keys {
		r, err = lp.lessor.TimeToLive(ctx, clientv3.LeaseID(rr.ID), clientv3.WithAttachedKeys())
	} else {
		r, err = lp.lessor.TimeToLive(ctx, clientv3.LeaseID(rr.ID))
	}
	if err != nil {
		return nil, err
	}
	rp := &pb.LeaseTimeToLiveResponse{
		Header:     r.ResponseHeader,
		ID:         int64(r.ID),
		TTL:        r.TTL,
		GrantedTTL: r.GrantedTTL,
		Keys:       r.Keys,
	}
	return rp, err
}

func (lp *leaseProxy) LeaseKeepAlive(stream pb.Lease_LeaseKeepAliveServer) error {
	lp.mu.Lock()
	select {
	case <-lp.ctx.Done():
		lp.mu.Unlock()
		return lp.ctx.Err()
	default:
		lp.wg.Add(1)
	}
	lp.mu.Unlock()

	ctx, cancel := context.WithCancel(stream.Context())
	lps := leaseProxyStream{
		stream:          stream,
		lessor:          lp.lessor,
		keepAliveLeases: make(map[int64]*atomicCounter),
		respc:           make(chan *pb.LeaseKeepAliveResponse),
		ctx:             ctx,
		cancel:          cancel,
	}

	errc := make(chan error, 2)

	var lostLeaderC <-chan struct{}
	if md, ok := metadata.FromOutgoingContext(stream.Context()); ok {
		v := md[rpctypes.MetadataRequireLeaderKey]
		if len(v) > 0 && v[0] == rpctypes.MetadataHasLeader {
			lostLeaderC = lp.leader.lostNotify()
			// if leader is known to be lost at creation time, avoid
			// letting events through at all
			select {
			case <-lostLeaderC:
				lp.wg.Done()
				return rpctypes.ErrNoLeader
			default:
			}
		}
	}
	stopc := make(chan struct{}, 3)
	go func() {
		defer func() { stopc <- struct{}{} }()
		if err := lps.recvLoop(); err != nil {
			errc <- err
		}
	}()

	go func() {
		defer func() { stopc <- struct{}{} }()
		if err := lps.sendLoop(); err != nil {
			errc <- err
		}
	}()

	// tears down LeaseKeepAlive stream if leader goes down or entire leaseProxy is terminated.
	go func() {
		defer func() { stopc <- struct{}{} }()
		select {
		case <-lostLeaderC:
		case <-ctx.Done():
		case <-lp.ctx.Done():
		}
	}()

	var err error
	select {
	case <-stopc:
		stopc <- struct{}{}
	case err = <-errc:
	}
	cancel()

	// recv/send may only shutdown after function exits;
	// this goroutine notifies lease proxy that the stream is through
	go func() {
		<-stopc
		<-stopc
		<-stopc
		lps.close()
		close(errc)
		lp.wg.Done()
	}()

	select {
	case <-lostLeaderC:
		return rpctypes.ErrNoLeader
	case <-lp.leader.disconnectNotify():
		return grpc.ErrClientConnClosing
	default:
		if err != nil {
			return err
		}
		return ctx.Err()
	}
}

type leaseProxyStream struct {
	stream pb.Lease_LeaseKeepAliveServer

	lessor clientv3.Lease
	// wg tracks keepAliveLoop goroutines
	wg sync.WaitGroup
	// mu protects keepAliveLeases
	mu sync.RWMutex
	// keepAliveLeases tracks how many outstanding keepalive requests which need responses are on a lease.
	keepAliveLeases map[int64]*atomicCounter
	// respc receives lease keepalive responses from etcd backend
	respc chan *pb.LeaseKeepAliveResponse

	ctx    context.Context
	cancel context.CancelFunc
}

func (lps *leaseProxyStream) recvLoop() error {
	for {
		rr, err := lps.stream.Recv()
		if err == io.EOF {
			return nil
		}
		if err != nil {
			return err
		}
		lps.mu.Lock()
		neededResps, ok := lps.keepAliveLeases[rr.ID]
		if !ok {
			neededResps = &atomicCounter{}
			lps.keepAliveLeases[rr.ID] = neededResps
			lps.wg.Add(1)
			go func() {
				defer lps.wg.Done()
				if err := lps.keepAliveLoop(rr.ID, neededResps); err != nil {
					lps.cancel()
				}
			}()
		}
		neededResps.add(1)
		lps.mu.Unlock()
	}
}

func (lps *leaseProxyStream) keepAliveLoop(leaseID int64, neededResps *atomicCounter) error {
	cctx, ccancel := context.WithCancel(lps.ctx)
	defer ccancel()
	respc, err := lps.lessor.KeepAlive(cctx, clientv3.LeaseID(leaseID))
	if err != nil {
		return err
	}
	// ticker expires when loop hasn't received keepalive within TTL
	var ticker <-chan time.Time
	for {
		select {
		case <-ticker:
			lps.mu.Lock()
			// if there are outstanding keepAlive reqs at the moment of ticker firing,
			// don't close keepAliveLoop(), let it continuing to process the KeepAlive reqs.
			if neededResps.get() > 0 {
				lps.mu.Unlock()
				ticker = nil
				continue
			}
			delete(lps.keepAliveLeases, leaseID)
			lps.mu.Unlock()
			return nil
		case rp, ok := <-respc:
			if !ok {
				lps.mu.Lock()
				delete(lps.keepAliveLeases, leaseID)
				lps.mu.Unlock()
				if neededResps.get() == 0 {
					return nil
				}
				ttlResp, err := lps.lessor.TimeToLive(cctx, clientv3.LeaseID(leaseID))
				if err != nil {
					return err
				}
				r := &pb.LeaseKeepAliveResponse{
					Header: ttlResp.ResponseHeader,
					ID:     int64(ttlResp.ID),
					TTL:    ttlResp.TTL,
				}
				for neededResps.get() > 0 {
					select {
					case lps.respc <- r:
						neededResps.add(-1)
					case <-lps.ctx.Done():
						return nil
					}
				}
				return nil
			}
			if neededResps.get() == 0 {
				continue
			}
			ticker = time.After(time.Duration(rp.TTL) * time.Second)
			r := &pb.LeaseKeepAliveResponse{
				Header: rp.ResponseHeader,
				ID:     int64(rp.ID),
				TTL:    rp.TTL,
			}
			lps.replyToClient(r, neededResps)
		}
	}
}

func (lps *leaseProxyStream) replyToClient(r *pb.LeaseKeepAliveResponse, neededResps *atomicCounter) {
	timer := time.After(500 * time.Millisecond)
	for neededResps.get() > 0 {
		select {
		case lps.respc <- r:
			neededResps.add(-1)
		case <-timer:
			return
		case <-lps.ctx.Done():
			return
		}
	}
}

func (lps *leaseProxyStream) sendLoop() error {
	for {
		select {
		case lrp, ok := <-lps.respc:
			if !ok {
				return nil
			}
			if err := lps.stream.Send(lrp); err != nil {
				return err
			}
		case <-lps.ctx.Done():
			return lps.ctx.Err()
		}
	}
}

func (lps *leaseProxyStream) close() {
	lps.cancel()
	lps.wg.Wait()
	// only close respc channel if all the keepAliveLoop() goroutines have finished
	// this ensures those goroutines don't send resp to a closed resp channel
	close(lps.respc)
}

type atomicCounter struct {
	counter int64
}

func (ac *atomicCounter) add(delta int64) {
	atomic.AddInt64(&ac.counter, delta)
}

func (ac *atomicCounter) get() int64 {
	return atomic.LoadInt64(&ac.counter)
}
<|endoftext|>


<commit_before>// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.

// +build linux

package notify

import (
	"bytes"
	"errors"
	"path/filepath"
	"runtime"
	"sync"
	"sync/atomic"
	"unsafe"

	"golang.org/x/sys/unix"
)

// eventBufferSize defines the size of the buffer given to read(2) function. One
// should not depend on this value, since it was arbitrary chosen and may be
// changed in the future.
const eventBufferSize = 64 * (unix.SizeofInotifyEvent + unix.PathMax + 1)

// consumersCount defines the number of consumers in producer-consumer based
// implementation. Each consumer is run in a separate goroutine and has read
// access to watched files map.
const consumersCount = 2

const invalidDescriptor = -1

// watched is a pair of file path and inotify mask used as a value in
// watched files map.
type watched struct {
	path string
	mask uint32
}

// inotify implements Watcher interface.
type inotify struct {
	sync.RWMutex                       // protects inotify.m map
	m            map[int32]*watched    // watch descriptor to watched object
	fd           int32                 // inotify file descriptor
	pipefd       []int                 // pipe's read and write descriptors
	epfd         int                   // epoll descriptor
	epes         []unix.EpollEvent     // epoll events
	buffer       [eventBufferSize]byte // inotify event buffer
	wg           sync.WaitGroup        // wait group used to close main loop
	c            chan<- EventInfo      // event dispatcher channel
}

// NewWatcher creates new non-recursive inotify backed by inotify.
func newWatcher(c chan<- EventInfo) watcher {
	i := &inotify{
		m:      make(map[int32]*watched),
		fd:     invalidDescriptor,
		pipefd: []int{invalidDescriptor, invalidDescriptor},
		epfd:   invalidDescriptor,
		epes:   make([]unix.EpollEvent, 0),
		c:      c,
	}
	runtime.SetFinalizer(i, func(i *inotify) {
		i.epollclose()
		if i.fd != invalidDescriptor {
			unix.Close(int(i.fd))
		}
	})
	return i
}

// Watch implements notify.watcher interface.
func (i *inotify) Watch(path string, e Event) error {
	return i.watch(path, e)
}

// Rewatch implements notify.watcher interface.
func (i *inotify) Rewatch(path string, _, newevent Event) error {
	return i.watch(path, newevent)
}

// watch adds a new watcher to the set of watched objects or modifies the existing
// one. If called for the first time, this function initializes inotify filesystem
// monitor and starts producer-consumers goroutines.
func (i *inotify) watch(path string, e Event) (err error) {
	if e&^(All|Event(unix.IN_ALL_EVENTS)) != 0 {
		return errors.New("notify: unknown event")
	}
	if err = i.lazyinit(); err != nil {
		return
	}
	iwd, err := unix.InotifyAddWatch(int(i.fd), path, encode(e))
	if err != nil {
		return
	}
	i.RLock()
	wd := i.m[int32(iwd)]
	i.RUnlock()
	if wd == nil {
		i.Lock()
		if i.m[int32(iwd)] == nil {
			i.m[int32(iwd)] = &watched{path: path, mask: uint32(e)}
		}
		i.Unlock()
	} else {
		i.Lock()
		wd.mask = uint32(e)
		i.Unlock()
	}
	return nil
}

// lazyinit sets up all required file descriptors and starts 1+consumersCount
// goroutines. The producer goroutine blocks until file-system notifications
// occur. Then, all events are read from system buffer and sent to consumer
// goroutines which construct valid notify events. This method uses
// Double-Checked Locking optimization.
func (i *inotify) lazyinit() error {
	if atomic.LoadInt32(&i.fd) == invalidDescriptor {
		i.Lock()
		defer i.Unlock()
		if atomic.LoadInt32(&i.fd) == invalidDescriptor {
			fd, err := unix.InotifyInit1(unix.IN_CLOEXEC)
			if err != nil {
				return err
			}
			i.fd = int32(fd)
			if err = i.epollinit(); err != nil {
				_, _ = i.epollclose(), unix.Close(int(fd)) // Ignore errors.
				i.fd = invalidDescriptor
				return err
			}
			esch := make(chan []*event)
			go i.loop(esch)
			i.wg.Add(consumersCount)
			for n := 0; n < consumersCount; n++ {
				go i.send(esch)
			}
		}
	}
	return nil
}

// epollinit opens an epoll file descriptor and creates a pipe which will be
// used to wake up the epoll_wait(2) function. Then, file descriptor associated
// with inotify event queue and the read end of the pipe are added to epoll set.
// Note that `fd` member must be set before this function is called.
func (i *inotify) epollinit() (err error) {
	if i.epfd, err = unix.EpollCreate1(0); err != nil {
		return
	}
	if err = unix.Pipe(i.pipefd); err != nil {
		return
	}
	i.epes = []unix.EpollEvent{
		{Events: unix.EPOLLIN, Fd: i.fd},
		{Events: unix.EPOLLIN, Fd: int32(i.pipefd[0])},
	}
	if err = unix.EpollCtl(i.epfd, unix.EPOLL_CTL_ADD, int(i.fd), &i.epes[0]); err != nil {
		return
	}
	return unix.EpollCtl(i.epfd, unix.EPOLL_CTL_ADD, i.pipefd[0], &i.epes[1])
}

// epollclose closes the file descriptor created by the call to epoll_create(2)
// and two file descriptors opened by pipe(2) function.
func (i *inotify) epollclose() (err error) {
	if i.epfd != invalidDescriptor {
		if err = unix.Close(i.epfd); err == nil {
			i.epfd = invalidDescriptor
		}
	}
	for n, fd := range i.pipefd {
		if fd != invalidDescriptor {
			switch e := unix.Close(fd); {
			case e != nil && err == nil:
				err = e
			case e == nil:
				i.pipefd[n] = invalidDescriptor
			}
		}
	}
	return
}

// loop blocks until either inotify or pipe file descriptor is ready for I/O.
// All read operations triggered by filesystem notifications are forwarded to
// one of the event's consumers. If pipe fd became ready, loop function closes
// all file descriptors opened by lazyinit method and returns afterwards.
func (i *inotify) loop(esch chan<- []*event) {
	epes := make([]unix.EpollEvent, 1)
	fd := atomic.LoadInt32(&i.fd)
	for {
		switch _, err := unix.EpollWait(i.epfd, epes, -1); err {
		case nil:
			switch epes[0].Fd {
			case fd:
				esch <- i.read()
				epes[0].Fd = 0
			case int32(i.pipefd[0]):
				i.Lock()
				defer i.Unlock()
				if err = unix.Close(int(fd)); err != nil && err != unix.EINTR {
					panic("notify: close(2) error " + err.Error())
				}
				atomic.StoreInt32(&i.fd, invalidDescriptor)
				if err = i.epollclose(); err != nil && err != unix.EINTR {
					panic("notify: epollclose error " + err.Error())
				}
				close(esch)
				return
			}
		case unix.EINTR:
			continue
		default: // We should never reach this line.
			panic("notify: epoll_wait(2) error " + err.Error())
		}
	}
}

// read reads events from an inotify file descriptor. It does not handle errors
// returned from read(2) function since they are not critical to watcher logic.
func (i *inotify) read() (es []*event) {
	n, err := unix.Read(int(i.fd), i.buffer[:])
	if err != nil || n < unix.SizeofInotifyEvent {
		return
	}
	var sys *unix.InotifyEvent
	nmin := n - unix.SizeofInotifyEvent
	for pos, path := 0, ""; pos <= nmin; {
		sys = (*unix.InotifyEvent)(unsafe.Pointer(&i.buffer[pos]))
		pos += unix.SizeofInotifyEvent
		if path = ""; sys.Len > 0 {
			endpos := pos + int(sys.Len)
			path = string(bytes.TrimRight(i.buffer[pos:endpos], "\x00"))
			pos = endpos
		}
		es = append(es, &event{
			sys: unix.InotifyEvent{
				Wd:     sys.Wd,
				Mask:   sys.Mask,
				Cookie: sys.Cookie,
			},
			path: path,
		})
	}
	return
}

// send is a consumer function which sends events to event dispatcher channel.
// It is run in a separate goroutine in order to not block loop method when
// possibly expensive write operations are performed on inotify map.
func (i *inotify) send(esch <-chan []*event) {
	for es := range esch {
		for _, e := range i.transform(es) {
			if e != nil {
				i.c <- e
			}
		}
	}
	i.wg.Done()
}

// transform prepares events read from inotify file descriptor for sending to
// user. It removes invalid events and these which are no longer present in
// inotify map. This method may also split one raw event into two different ones
// when system-dependent result is required.
func (i *inotify) transform(es []*event) []*event {
	var multi []*event
	i.RLock()
	for idx, e := range es {
		if e.sys.Mask&(unix.IN_IGNORED|unix.IN_Q_OVERFLOW) != 0 {
			es[idx] = nil
			continue
		}
		wd, ok := i.m[e.sys.Wd]
		if !ok || e.sys.Mask&encode(Event(wd.mask)) == 0 {
			es[idx] = nil
			continue
		}
		if e.path == "" {
			e.path = wd.path
		} else {
			e.path = filepath.Join(wd.path, e.path)
		}
		multi = append(multi, decode(Event(wd.mask), e))
		if e.event == 0 {
			es[idx] = nil
		}
	}
	i.RUnlock()
	es = append(es, multi...)
	return es
}

// encode converts notify system-independent events to valid inotify mask
// which can be passed to inotify_add_watch(2) function.
func encode(e Event) uint32 {
	if e&Create != 0 {
		e = (e ^ Create) | InCreate | InMovedTo
	}
	if e&Remove != 0 {
		e = (e ^ Remove) | InDelete | InDeleteSelf
	}
	if e&Write != 0 {
		e = (e ^ Write) | InModify
	}
	if e&Rename != 0 {
		e = (e ^ Rename) | InMovedFrom | InMoveSelf
	}
	return uint32(e)
}

// decode uses internally stored mask to distinguish whether system-independent
// or system-dependent event is requested. The first one is created by modifying
// `e` argument. decode method sets e.event value to 0 when an event should be
// skipped. System-dependent event is set as the function's return value which
// can be nil when the event should not be passed on.
func decode(mask Event, e *event) (syse *event) {
	if sysmask := uint32(mask) & e.sys.Mask; sysmask != 0 {
		syse = &event{sys: unix.InotifyEvent{
			Wd:     e.sys.Wd,
			Mask:   e.sys.Mask,
			Cookie: e.sys.Cookie,
		}, event: Event(sysmask), path: e.path}
	}
	imask := encode(mask)
	switch {
	case mask&Create != 0 && imask&uint32(InCreate|InMovedTo)&e.sys.Mask != 0:
		e.event = Create
	case mask&Remove != 0 && imask&uint32(InDelete|InDeleteSelf)&e.sys.Mask != 0:
		e.event = Remove
	case mask&Write != 0 && imask&uint32(InModify)&e.sys.Mask != 0:
		e.event = Write
	case mask&Rename != 0 && imask&uint32(InMovedFrom|InMoveSelf)&e.sys.Mask != 0:
		e.event = Rename
	default:
		e.event = 0
	}
	return
}

// Unwatch implements notify.watcher interface. It looks for watch descriptor
// related to registered path and if found, calls inotify_rm_watch(2) function.
// This method is allowed to return EINVAL error when concurrently requested to
// delete identical path.
func (i *inotify) Unwatch(path string) (err error) {
	iwd := int32(invalidDescriptor)
	i.RLock()
	for iwdkey, wd := range i.m {
		if wd.path == path {
			iwd = iwdkey
			break
		}
	}
	i.RUnlock()
	if iwd == invalidDescriptor {
		return errors.New("notify: path " + path + " is already watched")
	}
	fd := atomic.LoadInt32(&i.fd)
	if _, err = unix.InotifyRmWatch(int(fd), uint32(iwd)); err != nil {
		return
	}
	i.Lock()
	delete(i.m, iwd)
	i.Unlock()
	return nil
}

// Close implements notify.watcher interface. It removes all existing watch
// descriptors and wakes up producer goroutine by sending data to the write end
// of the pipe. The function waits for a signal from producer which means that
// all operations on current monitoring instance are done.
func (i *inotify) Close() (err error) {
	i.Lock()
	if fd := atomic.LoadInt32(&i.fd); fd == invalidDescriptor {
		i.Unlock()
		return nil
	}
	for iwd := range i.m {
		if _, e := unix.InotifyRmWatch(int(i.fd), uint32(iwd)); e != nil && err == nil {
			err = e
		}
		delete(i.m, iwd)
	}
	switch _, errwrite := unix.Write(i.pipefd[1], []byte{0x00}); {
	case errwrite != nil && err == nil:
		err = errwrite
		fallthrough
	case errwrite != nil:
		i.Unlock()
	default:
		i.Unlock()
		i.wg.Wait()
	}
	return
}
<commit_msg>inotify: Handle removing of watches on non-existing paths<commit_after>// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.

// +build linux

package notify

import (
	"bytes"
	"errors"
	"path/filepath"
	"runtime"
	"sync"
	"sync/atomic"
	"unsafe"

	"golang.org/x/sys/unix"
)

// eventBufferSize defines the size of the buffer given to read(2) function. One
// should not depend on this value, since it was arbitrary chosen and may be
// changed in the future.
const eventBufferSize = 64 * (unix.SizeofInotifyEvent + unix.PathMax + 1)

// consumersCount defines the number of consumers in producer-consumer based
// implementation. Each consumer is run in a separate goroutine and has read
// access to watched files map.
const consumersCount = 2

const invalidDescriptor = -1

// watched is a pair of file path and inotify mask used as a value in
// watched files map.
type watched struct {
	path string
	mask uint32
}

// inotify implements Watcher interface.
type inotify struct {
	sync.RWMutex                       // protects inotify.m map
	m            map[int32]*watched    // watch descriptor to watched object
	fd           int32                 // inotify file descriptor
	pipefd       []int                 // pipe's read and write descriptors
	epfd         int                   // epoll descriptor
	epes         []unix.EpollEvent     // epoll events
	buffer       [eventBufferSize]byte // inotify event buffer
	wg           sync.WaitGroup        // wait group used to close main loop
	c            chan<- EventInfo      // event dispatcher channel
}

// NewWatcher creates new non-recursive inotify backed by inotify.
func newWatcher(c chan<- EventInfo) watcher {
	i := &inotify{
		m:      make(map[int32]*watched),
		fd:     invalidDescriptor,
		pipefd: []int{invalidDescriptor, invalidDescriptor},
		epfd:   invalidDescriptor,
		epes:   make([]unix.EpollEvent, 0),
		c:      c,
	}
	runtime.SetFinalizer(i, func(i *inotify) {
		i.epollclose()
		if i.fd != invalidDescriptor {
			unix.Close(int(i.fd))
		}
	})
	return i
}

// Watch implements notify.watcher interface.
func (i *inotify) Watch(path string, e Event) error {
	return i.watch(path, e)
}

// Rewatch implements notify.watcher interface.
func (i *inotify) Rewatch(path string, _, newevent Event) error {
	return i.watch(path, newevent)
}

// watch adds a new watcher to the set of watched objects or modifies the existing
// one. If called for the first time, this function initializes inotify filesystem
// monitor and starts producer-consumers goroutines.
func (i *inotify) watch(path string, e Event) (err error) {
	if e&^(All|Event(unix.IN_ALL_EVENTS)) != 0 {
		return errors.New("notify: unknown event")
	}
	if err = i.lazyinit(); err != nil {
		return
	}
	iwd, err := unix.InotifyAddWatch(int(i.fd), path, encode(e))
	if err != nil {
		return
	}
	i.RLock()
	wd := i.m[int32(iwd)]
	i.RUnlock()
	if wd == nil {
		i.Lock()
		if i.m[int32(iwd)] == nil {
			i.m[int32(iwd)] = &watched{path: path, mask: uint32(e)}
		}
		i.Unlock()
	} else {
		i.Lock()
		wd.mask = uint32(e)
		i.Unlock()
	}
	return nil
}

// lazyinit sets up all required file descriptors and starts 1+consumersCount
// goroutines. The producer goroutine blocks until file-system notifications
// occur. Then, all events are read from system buffer and sent to consumer
// goroutines which construct valid notify events. This method uses
// Double-Checked Locking optimization.
func (i *inotify) lazyinit() error {
	if atomic.LoadInt32(&i.fd) == invalidDescriptor {
		i.Lock()
		defer i.Unlock()
		if atomic.LoadInt32(&i.fd) == invalidDescriptor {
			fd, err := unix.InotifyInit1(unix.IN_CLOEXEC)
			if err != nil {
				return err
			}
			i.fd = int32(fd)
			if err = i.epollinit(); err != nil {
				_, _ = i.epollclose(), unix.Close(int(fd)) // Ignore errors.
				i.fd = invalidDescriptor
				return err
			}
			esch := make(chan []*event)
			go i.loop(esch)
			i.wg.Add(consumersCount)
			for n := 0; n < consumersCount; n++ {
				go i.send(esch)
			}
		}
	}
	return nil
}

// epollinit opens an epoll file descriptor and creates a pipe which will be
// used to wake up the epoll_wait(2) function. Then, file descriptor associated
// with inotify event queue and the read end of the pipe are added to epoll set.
// Note that `fd` member must be set before this function is called.
func (i *inotify) epollinit() (err error) {
	if i.epfd, err = unix.EpollCreate1(0); err != nil {
		return
	}
	if err = unix.Pipe(i.pipefd); err != nil {
		return
	}
	i.epes = []unix.EpollEvent{
		{Events: unix.EPOLLIN, Fd: i.fd},
		{Events: unix.EPOLLIN, Fd: int32(i.pipefd[0])},
	}
	if err = unix.EpollCtl(i.epfd, unix.EPOLL_CTL_ADD, int(i.fd), &i.epes[0]); err != nil {
		return
	}
	return unix.EpollCtl(i.epfd, unix.EPOLL_CTL_ADD, i.pipefd[0], &i.epes[1])
}

// epollclose closes the file descriptor created by the call to epoll_create(2)
// and two file descriptors opened by pipe(2) function.
func (i *inotify) epollclose() (err error) {
	if i.epfd != invalidDescriptor {
		if err = unix.Close(i.epfd); err == nil {
			i.epfd = invalidDescriptor
		}
	}
	for n, fd := range i.pipefd {
		if fd != invalidDescriptor {
			switch e := unix.Close(fd); {
			case e != nil && err == nil:
				err = e
			case e == nil:
				i.pipefd[n] = invalidDescriptor
			}
		}
	}
	return
}

// loop blocks until either inotify or pipe file descriptor is ready for I/O.
// All read operations triggered by filesystem notifications are forwarded to
// one of the event's consumers. If pipe fd became ready, loop function closes
// all file descriptors opened by lazyinit method and returns afterwards.
func (i *inotify) loop(esch chan<- []*event) {
	epes := make([]unix.EpollEvent, 1)
	fd := atomic.LoadInt32(&i.fd)
	for {
		switch _, err := unix.EpollWait(i.epfd, epes, -1); err {
		case nil:
			switch epes[0].Fd {
			case fd:
				esch <- i.read()
				epes[0].Fd = 0
			case int32(i.pipefd[0]):
				i.Lock()
				defer i.Unlock()
				if err = unix.Close(int(fd)); err != nil && err != unix.EINTR {
					panic("notify: close(2) error " + err.Error())
				}
				atomic.StoreInt32(&i.fd, invalidDescriptor)
				if err = i.epollclose(); err != nil && err != unix.EINTR {
					panic("notify: epollclose error " + err.Error())
				}
				close(esch)
				return
			}
		case unix.EINTR:
			continue
		default: // We should never reach this line.
			panic("notify: epoll_wait(2) error " + err.Error())
		}
	}
}

// read reads events from an inotify file descriptor. It does not handle errors
// returned from read(2) function since they are not critical to watcher logic.
func (i *inotify) read() (es []*event) {
	n, err := unix.Read(int(i.fd), i.buffer[:])
	if err != nil || n < unix.SizeofInotifyEvent {
		return
	}
	var sys *unix.InotifyEvent
	nmin := n - unix.SizeofInotifyEvent
	for pos, path := 0, ""; pos <= nmin; {
		sys = (*unix.InotifyEvent)(unsafe.Pointer(&i.buffer[pos]))
		pos += unix.SizeofInotifyEvent
		if path = ""; sys.Len > 0 {
			endpos := pos + int(sys.Len)
			path = string(bytes.TrimRight(i.buffer[pos:endpos], "\x00"))
			pos = endpos
		}
		es = append(es, &event{
			sys: unix.InotifyEvent{
				Wd:     sys.Wd,
				Mask:   sys.Mask,
				Cookie: sys.Cookie,
			},
			path: path,
		})
	}
	return
}

// send is a consumer function which sends events to event dispatcher channel.
// It is run in a separate goroutine in order to not block loop method when
// possibly expensive write operations are performed on inotify map.
func (i *inotify) send(esch <-chan []*event) {
	for es := range esch {
		for _, e := range i.transform(es) {
			if e != nil {
				i.c <- e
			}
		}
	}
	i.wg.Done()
}

// transform prepares events read from inotify file descriptor for sending to
// user. It removes invalid events and these which are no longer present in
// inotify map. This method may also split one raw event into two different ones
// when system-dependent result is required.
func (i *inotify) transform(es []*event) []*event {
	var multi []*event
	i.RLock()
	for idx, e := range es {
		if e.sys.Mask&(unix.IN_IGNORED|unix.IN_Q_OVERFLOW) != 0 {
			es[idx] = nil
			continue
		}
		wd, ok := i.m[e.sys.Wd]
		if !ok || e.sys.Mask&encode(Event(wd.mask)) == 0 {
			es[idx] = nil
			continue
		}
		if e.path == "" {
			e.path = wd.path
		} else {
			e.path = filepath.Join(wd.path, e.path)
		}
		multi = append(multi, decode(Event(wd.mask), e))
		if e.event == 0 {
			es[idx] = nil
		}
	}
	i.RUnlock()
	es = append(es, multi...)
	return es
}

// encode converts notify system-independent events to valid inotify mask
// which can be passed to inotify_add_watch(2) function.
func encode(e Event) uint32 {
	if e&Create != 0 {
		e = (e ^ Create) | InCreate | InMovedTo
	}
	if e&Remove != 0 {
		e = (e ^ Remove) | InDelete | InDeleteSelf
	}
	if e&Write != 0 {
		e = (e ^ Write) | InModify
	}
	if e&Rename != 0 {
		e = (e ^ Rename) | InMovedFrom | InMoveSelf
	}
	return uint32(e)
}

// decode uses internally stored mask to distinguish whether system-independent
// or system-dependent event is requested. The first one is created by modifying
// `e` argument. decode method sets e.event value to 0 when an event should be
// skipped. System-dependent event is set as the function's return value which
// can be nil when the event should not be passed on.
func decode(mask Event, e *event) (syse *event) {
	if sysmask := uint32(mask) & e.sys.Mask; sysmask != 0 {
		syse = &event{sys: unix.InotifyEvent{
			Wd:     e.sys.Wd,
			Mask:   e.sys.Mask,
			Cookie: e.sys.Cookie,
		}, event: Event(sysmask), path: e.path}
	}
	imask := encode(mask)
	switch {
	case mask&Create != 0 && imask&uint32(InCreate|InMovedTo)&e.sys.Mask != 0:
		e.event = Create
	case mask&Remove != 0 && imask&uint32(InDelete|InDeleteSelf)&e.sys.Mask != 0:
		e.event = Remove
	case mask&Write != 0 && imask&uint32(InModify)&e.sys.Mask != 0:
		e.event = Write
	case mask&Rename != 0 && imask&uint32(InMovedFrom|InMoveSelf)&e.sys.Mask != 0:
		e.event = Rename
	default:
		e.event = 0
	}
	return
}

// Unwatch implements notify.watcher interface. It looks for watch descriptor
// related to registered path and if found, calls inotify_rm_watch(2) function.
// This method is allowed to return EINVAL error when concurrently requested to
// delete identical path.
func (i *inotify) Unwatch(path string) (err error) {
	iwd := int32(invalidDescriptor)
	i.RLock()
	for iwdkey, wd := range i.m {
		if wd.path == path {
			iwd = iwdkey
			break
		}
	}
	i.RUnlock()
	if iwd == invalidDescriptor {
		return errors.New("notify: path " + path + " is already watched")
	}
	fd := atomic.LoadInt32(&i.fd)
	if err = removeInotifyWatch(fd, iwd); err != nil {
		return
	}
	i.Lock()
	delete(i.m, iwd)
	i.Unlock()
	return nil
}

// Close implements notify.watcher interface. It removes all existing watch
// descriptors and wakes up producer goroutine by sending data to the write end
// of the pipe. The function waits for a signal from producer which means that
// all operations on current monitoring instance are done.
func (i *inotify) Close() (err error) {
	i.Lock()
	if fd := atomic.LoadInt32(&i.fd); fd == invalidDescriptor {
		i.Unlock()
		return nil
	}
	for iwd := range i.m {
		if e := removeInotifyWatch(i.fd, iwd); e != nil && err == nil {
			err = e
		}
		delete(i.m, iwd)
	}
	switch _, errwrite := unix.Write(i.pipefd[1], []byte{0x00}); {
	case errwrite != nil && err == nil:
		err = errwrite
		fallthrough
	case errwrite != nil:
		i.Unlock()
	default:
		i.Unlock()
		i.wg.Wait()
	}
	return
}

// if path was removed, notify already removed the watch and returns EINVAL error
func removeInotifyWatch(fd int32, iwd int32) (err error) {
	if _, err = unix.InotifyRmWatch(int(fd), uint32(iwd)); err != nil && err != unix.EINVAL {
		return
	}
	return nil
}
<|endoftext|>