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

package state_test

import (
	"sort"
	"sync"
	"sync/atomic"

	"github.com/juju/errors"
	jc "github.com/juju/testing/checkers"
	gc "gopkg.in/check.v1"

	"github.com/juju/juju/network"
	"github.com/juju/juju/state"
)

type SubnetSuite struct {
	ConnSuite
}

var _ = gc.Suite(&SubnetSuite{})

func (s *SubnetSuite) TestAddSubnet(c *gc.C) {
	subnetInfo := state.SubnetInfo{
		ProviderId:        "foo",
		CIDR:              "192.168.1.0/24",
		VLANTag:           79,
		AllocatableIPLow:  "192.168.1.0",
		AllocatableIPHigh: "192.168.1.1",
		AvailabilityZone:  "Timbuktu",
	}

	assertSubnet := func(subnet *state.Subnet) {
		c.Assert(subnet.ProviderId(), gc.Equals, "foo")
		c.Assert(subnet.CIDR(), gc.Equals, "192.168.1.0/24")
		c.Assert(subnet.VLANTag(), gc.Equals, 79)
		c.Assert(subnet.AllocatableIPLow(), gc.Equals, "192.168.1.0")
		c.Assert(subnet.AllocatableIPHigh(), gc.Equals, "192.168.1.1")
		c.Assert(subnet.AvailabilityZone(), gc.Equals, "Timbuktu")
	}

	subnet, err := s.State.AddSubnet(subnetInfo)
	c.Assert(err, jc.ErrorIsNil)
	assertSubnet(subnet)

	// check it's been stored in state by fetching it back again
	subnetFromDB, err := s.State.Subnet("192.168.1.0/24")
	c.Assert(err, jc.ErrorIsNil)
	assertSubnet(subnetFromDB)
}

func (s *SubnetSuite) TestAddSubnetErrors(c *gc.C) {
	subnetInfo := state.SubnetInfo{}
	_, err := s.State.AddSubnet(subnetInfo)
	c.Assert(errors.Cause(err), gc.ErrorMatches, "missing CIDR")

	subnetInfo.CIDR = "foobar"
	_, err = s.State.AddSubnet(subnetInfo)
	c.Assert(errors.Cause(err), gc.ErrorMatches, "invalid CIDR address: foobar")

	subnetInfo.CIDR = "192.168.0.1/24"
	subnetInfo.VLANTag = 4095
	_, err = s.State.AddSubnet(subnetInfo)
	c.Assert(errors.Cause(err), gc.ErrorMatches, "invalid VLAN tag 4095: must be between 0 and 4094")

	eitherOrMsg := "either both AllocatableIPLow and AllocatableIPHigh must be set or neither set"
	subnetInfo.VLANTag = 0
	subnetInfo.AllocatableIPHigh = "192.168.0.1"
	_, err = s.State.AddSubnet(subnetInfo)
	c.Assert(errors.Cause(err), gc.ErrorMatches, eitherOrMsg)

	subnetInfo.AllocatableIPLow = "192.168.0.1"
	subnetInfo.AllocatableIPHigh = ""
	_, err = s.State.AddSubnet(subnetInfo)
	c.Assert(errors.Cause(err), gc.ErrorMatches, eitherOrMsg)

	// invalid IP address
	subnetInfo.AllocatableIPHigh = "foobar"
	_, err = s.State.AddSubnet(subnetInfo)
	c.Assert(errors.Cause(err), gc.ErrorMatches, `invalid AllocatableIPHigh \"foobar\"`)

	// invalid IP address
	subnetInfo.AllocatableIPLow = "foobar"
	subnetInfo.AllocatableIPHigh = "192.168.0.1"
	_, err = s.State.AddSubnet(subnetInfo)
	c.Assert(errors.Cause(err), gc.ErrorMatches, `invalid AllocatableIPLow "foobar"`)

	// IP address out of range
	subnetInfo.AllocatableIPHigh = "172.168.1.0"
	_, err = s.State.AddSubnet(subnetInfo)
	c.Assert(errors.Cause(err), gc.ErrorMatches, `invalid AllocatableIPHigh "172.168.1.0"`)

	// IP address out of range
	subnetInfo.AllocatableIPHigh = "192.168.0.1"
	subnetInfo.AllocatableIPLow = "172.168.1.0"
	_, err = s.State.AddSubnet(subnetInfo)
	c.Assert(errors.Cause(err), gc.ErrorMatches, `invalid AllocatableIPLow "172.168.1.0"`)

	// valid case
	subnetInfo.AllocatableIPLow = "192.168.0.1"
	subnetInfo.ProviderId = "testing uniqueness"
	_, err = s.State.AddSubnet(subnetInfo)
	c.Assert(err, jc.ErrorIsNil)

	_, err = s.State.AddSubnet(subnetInfo)
	c.Assert(errors.IsAlreadyExists(err), jc.IsTrue)

	// ProviderId should be unique as well as CIDR
	subnetInfo.CIDR = "192.0.0.0/0"
	_, err = s.State.AddSubnet(subnetInfo)
	c.Assert(err, gc.ErrorMatches, `.*ProviderId not unique "testing uniqueness".*`)

	// empty provider id should be allowed to be not unique
	subnetInfo.ProviderId = ""
	_, err = s.State.AddSubnet(subnetInfo)
	c.Assert(err, jc.ErrorIsNil)
	subnetInfo.CIDR = "192.0.0.1/1"
	_, err = s.State.AddSubnet(subnetInfo)
	c.Assert(err, jc.ErrorIsNil)
}

func (s *SubnetSuite) TestSubnetEnsureDeadRemove(c *gc.C) {
	subnetInfo := state.SubnetInfo{CIDR: "192.168.1.0/24"}

	subnet, err := s.State.AddSubnet(subnetInfo)
	c.Assert(err, jc.ErrorIsNil)

	// This should fail - not dead yet!
	err = subnet.Remove()
	c.Assert(err, gc.ErrorMatches, ".*subnet is not dead.*")

	err = subnet.EnsureDead()
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(subnet.Life(), gc.Equals, state.Dead)

	// EnsureDead a second time should also not be an error
	err = subnet.EnsureDead()
	c.Assert(err, jc.ErrorIsNil)

	// check the change was persisted
	subnetCopy, err := s.State.Subnet("192.168.1.0/24")
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(subnetCopy.Life(), gc.Equals, state.Dead)

	// Remove should now work
	err = subnet.Remove()
	c.Assert(err, jc.ErrorIsNil)

	_, err = s.State.Subnet("192.168.1.0/24")
	c.Assert(err, gc.ErrorMatches, `.*subnet "192.168.1.0/24" not found.*`)

	// removing a second time should be a no-op
	err = subnet.Remove()
	c.Assert(err, jc.ErrorIsNil)
}

func (s *SubnetSuite) TestSubnetRemoveKillsAddresses(c *gc.C) {
	subnetInfo := state.SubnetInfo{CIDR: "192.168.1.0/24"}
	subnet, err := s.State.AddSubnet(subnetInfo)
	c.Assert(err, jc.ErrorIsNil)

	_, err = s.State.AddIPAddress(network.NewAddress("192.168.1.0", ""), subnet.ID())
	c.Assert(err, jc.ErrorIsNil)
	_, err = s.State.AddIPAddress(network.NewAddress("192.168.1.1", ""), subnet.ID())
	c.Assert(err, jc.ErrorIsNil)

	err = subnet.EnsureDead()
	c.Assert(err, jc.ErrorIsNil)
	err = subnet.Remove()
	c.Assert(err, jc.ErrorIsNil)

	_, err = s.State.IPAddress("192.168.1.0")
	c.Assert(errors.Cause(err), jc.Satisfies, errors.IsNotFound)
	_, err = s.State.IPAddress("192.168.1.1")
	c.Assert(errors.Cause(err), jc.Satisfies, errors.IsNotFound)
}

func (s *SubnetSuite) TestRefresh(c *gc.C) {
	subnetInfo := state.SubnetInfo{CIDR: "192.168.1.0/24"}

	subnet, err := s.State.AddSubnet(subnetInfo)
	c.Assert(err, jc.ErrorIsNil)

	subnetCopy, err := s.State.Subnet("192.168.1.0/24")
	c.Assert(err, jc.ErrorIsNil)

	err = subnet.EnsureDead()
	c.Assert(err, jc.ErrorIsNil)

	c.Assert(subnetCopy.Life(), gc.Equals, state.Alive)
	err = subnetCopy.Refresh()
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(subnetCopy.Life(), gc.Equals, state.Dead)
}

func (s *SubnetSuite) TestPickNewAddressNoAddresses(c *gc.C) {
	subnetInfo := state.SubnetInfo{
		CIDR:              "192.168.1.0/24",
		AllocatableIPLow:  "",
		AllocatableIPHigh: "",
	}
	subnet, err := s.State.AddSubnet(subnetInfo)
	c.Assert(err, jc.ErrorIsNil)

	_, err = subnet.PickNewAddress()
	c.Assert(err, gc.ErrorMatches, "no allocatable IP addresses for subnet .*")
}

func (s *SubnetSuite) getSubnetForAddressPicking(c *gc.C, allocatableHigh string) *state.Subnet {
	subnetInfo := state.SubnetInfo{
		CIDR:              "192.168.1.0/24",
		AllocatableIPLow:  "192.168.1.0",
		AllocatableIPHigh: allocatableHigh,
	}
	subnet, err := s.State.AddSubnet(subnetInfo)
	c.Assert(err, jc.ErrorIsNil)
	return subnet
}

func (s *SubnetSuite) TestPickNewAddressAddressesExhausted(c *gc.C) {
	subnet := s.getSubnetForAddressPicking(c, "192.168.1.0")
	addr := network.NewAddress("192.168.1.0", network.ScopeUnknown)
	_, err := s.State.AddIPAddress(addr, subnet.ID())

	_, err = subnet.PickNewAddress()
	c.Assert(err, gc.ErrorMatches, "allocatable IP addresses exhausted for subnet .*")
}

func (s *SubnetSuite) TestPickNewAddressOneAddress(c *gc.C) {
	subnet := s.getSubnetForAddressPicking(c, "192.168.1.0")

	addr, err := subnet.PickNewAddress()
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(addr.Value(), gc.Equals, "192.168.1.0")
}

func (s *SubnetSuite) TestPickNewAddress(c *gc.C) {
	subnet := s.getSubnetForAddressPicking(c, "192.168.1.1")

	addr := network.NewAddress("192.168.1.0", network.ScopeUnknown)
	_, err := s.State.AddIPAddress(addr, subnet.ID())

	ipAddr, err := subnet.PickNewAddress()
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(ipAddr.Value(), gc.Equals, "192.168.1.1")
}

func (s *SubnetSuite) TestPickNewAddressRace(c *gc.C) {
	// represents 192.168.1.0
	initialIP := uint32(3232235776)
	var index int32 = -1
	addresses := []uint32{initialIP, initialIP, initialIP + 1}

	// the first two calls will get the same address (which simulates the
	// inherent race condition in the code). The third call will get
	// a new one. We should see two different addresses come out of the
	// two calls: i.e. we will have detected the race condition and tried
	// again.
	mockPickAddress := func(_, _ uint32, _ map[uint32]bool) uint32 {
		theIndex := atomic.AddInt32(&index, 1)
		return addresses[theIndex]
	}
	s.PatchValue(&state.PickAddress, &mockPickAddress)

	// 192.168.1.0 and 192.168.1.1 are the only valid addresses
	subnet := s.getSubnetForAddressPicking(c, "192.168.1.1")

	waiter := sync.WaitGroup{}
	waiter.Add(2)

	var firstResult *state.IPAddress
	var firstError error
	var secondResult *state.IPAddress
	var secondError error
	go func() {
		firstResult, firstError = subnet.PickNewAddress()
		waiter.Done()
	}()
	go func() {
		secondResult, secondError = subnet.PickNewAddress()
		waiter.Done()
	}()
	waiter.Wait()

	c.Assert(firstError, jc.ErrorIsNil)
	c.Assert(secondError, jc.ErrorIsNil)
	c.Assert(firstResult, gc.NotNil)
	c.Assert(secondResult, gc.NotNil)

	ipAddresses := []string{firstResult.Value(), secondResult.Value()}
	sort.Strings(ipAddresses)

	expected := []string{"192.168.1.0", "192.168.1.1"}
	c.Assert(ipAddresses, jc.DeepEquals, expected)
}
<commit_msg>Test rename<commit_after>// Copyright 2014 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package state_test

import (
	"sort"
	"sync"
	"sync/atomic"

	"github.com/juju/errors"
	jc "github.com/juju/testing/checkers"
	gc "gopkg.in/check.v1"

	"github.com/juju/juju/network"
	"github.com/juju/juju/state"
)

type SubnetSuite struct {
	ConnSuite
}

var _ = gc.Suite(&SubnetSuite{})

func (s *SubnetSuite) TestAddSubnet(c *gc.C) {
	subnetInfo := state.SubnetInfo{
		ProviderId:        "foo",
		CIDR:              "192.168.1.0/24",
		VLANTag:           79,
		AllocatableIPLow:  "192.168.1.0",
		AllocatableIPHigh: "192.168.1.1",
		AvailabilityZone:  "Timbuktu",
	}

	assertSubnet := func(subnet *state.Subnet) {
		c.Assert(subnet.ProviderId(), gc.Equals, "foo")
		c.Assert(subnet.CIDR(), gc.Equals, "192.168.1.0/24")
		c.Assert(subnet.VLANTag(), gc.Equals, 79)
		c.Assert(subnet.AllocatableIPLow(), gc.Equals, "192.168.1.0")
		c.Assert(subnet.AllocatableIPHigh(), gc.Equals, "192.168.1.1")
		c.Assert(subnet.AvailabilityZone(), gc.Equals, "Timbuktu")
	}

	subnet, err := s.State.AddSubnet(subnetInfo)
	c.Assert(err, jc.ErrorIsNil)
	assertSubnet(subnet)

	// check it's been stored in state by fetching it back again
	subnetFromDB, err := s.State.Subnet("192.168.1.0/24")
	c.Assert(err, jc.ErrorIsNil)
	assertSubnet(subnetFromDB)
}

func (s *SubnetSuite) TestAddSubnetErrors(c *gc.C) {
	subnetInfo := state.SubnetInfo{}
	_, err := s.State.AddSubnet(subnetInfo)
	c.Assert(errors.Cause(err), gc.ErrorMatches, "missing CIDR")

	subnetInfo.CIDR = "foobar"
	_, err = s.State.AddSubnet(subnetInfo)
	c.Assert(errors.Cause(err), gc.ErrorMatches, "invalid CIDR address: foobar")

	subnetInfo.CIDR = "192.168.0.1/24"
	subnetInfo.VLANTag = 4095
	_, err = s.State.AddSubnet(subnetInfo)
	c.Assert(errors.Cause(err), gc.ErrorMatches, "invalid VLAN tag 4095: must be between 0 and 4094")

	eitherOrMsg := "either both AllocatableIPLow and AllocatableIPHigh must be set or neither set"
	subnetInfo.VLANTag = 0
	subnetInfo.AllocatableIPHigh = "192.168.0.1"
	_, err = s.State.AddSubnet(subnetInfo)
	c.Assert(errors.Cause(err), gc.ErrorMatches, eitherOrMsg)

	subnetInfo.AllocatableIPLow = "192.168.0.1"
	subnetInfo.AllocatableIPHigh = ""
	_, err = s.State.AddSubnet(subnetInfo)
	c.Assert(errors.Cause(err), gc.ErrorMatches, eitherOrMsg)

	// invalid IP address
	subnetInfo.AllocatableIPHigh = "foobar"
	_, err = s.State.AddSubnet(subnetInfo)
	c.Assert(errors.Cause(err), gc.ErrorMatches, `invalid AllocatableIPHigh \"foobar\"`)

	// invalid IP address
	subnetInfo.AllocatableIPLow = "foobar"
	subnetInfo.AllocatableIPHigh = "192.168.0.1"
	_, err = s.State.AddSubnet(subnetInfo)
	c.Assert(errors.Cause(err), gc.ErrorMatches, `invalid AllocatableIPLow "foobar"`)

	// IP address out of range
	subnetInfo.AllocatableIPHigh = "172.168.1.0"
	_, err = s.State.AddSubnet(subnetInfo)
	c.Assert(errors.Cause(err), gc.ErrorMatches, `invalid AllocatableIPHigh "172.168.1.0"`)

	// IP address out of range
	subnetInfo.AllocatableIPHigh = "192.168.0.1"
	subnetInfo.AllocatableIPLow = "172.168.1.0"
	_, err = s.State.AddSubnet(subnetInfo)
	c.Assert(errors.Cause(err), gc.ErrorMatches, `invalid AllocatableIPLow "172.168.1.0"`)

	// valid case
	subnetInfo.AllocatableIPLow = "192.168.0.1"
	subnetInfo.ProviderId = "testing uniqueness"
	_, err = s.State.AddSubnet(subnetInfo)
	c.Assert(err, jc.ErrorIsNil)

	_, err = s.State.AddSubnet(subnetInfo)
	c.Assert(errors.IsAlreadyExists(err), jc.IsTrue)

	// ProviderId should be unique as well as CIDR
	subnetInfo.CIDR = "192.0.0.0/0"
	_, err = s.State.AddSubnet(subnetInfo)
	c.Assert(err, gc.ErrorMatches, `.*ProviderId not unique "testing uniqueness".*`)

	// empty provider id should be allowed to be not unique
	subnetInfo.ProviderId = ""
	_, err = s.State.AddSubnet(subnetInfo)
	c.Assert(err, jc.ErrorIsNil)
	subnetInfo.CIDR = "192.0.0.1/1"
	_, err = s.State.AddSubnet(subnetInfo)
	c.Assert(err, jc.ErrorIsNil)
}

func (s *SubnetSuite) TestSubnetEnsureDeadRemove(c *gc.C) {
	subnetInfo := state.SubnetInfo{CIDR: "192.168.1.0/24"}

	subnet, err := s.State.AddSubnet(subnetInfo)
	c.Assert(err, jc.ErrorIsNil)

	// This should fail - not dead yet!
	err = subnet.Remove()
	c.Assert(err, gc.ErrorMatches, ".*subnet is not dead.*")

	err = subnet.EnsureDead()
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(subnet.Life(), gc.Equals, state.Dead)

	// EnsureDead a second time should also not be an error
	err = subnet.EnsureDead()
	c.Assert(err, jc.ErrorIsNil)

	// check the change was persisted
	subnetCopy, err := s.State.Subnet("192.168.1.0/24")
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(subnetCopy.Life(), gc.Equals, state.Dead)

	// Remove should now work
	err = subnet.Remove()
	c.Assert(err, jc.ErrorIsNil)

	_, err = s.State.Subnet("192.168.1.0/24")
	c.Assert(err, gc.ErrorMatches, `.*subnet "192.168.1.0/24" not found.*`)

	// removing a second time should be a no-op
	err = subnet.Remove()
	c.Assert(err, jc.ErrorIsNil)
}

func (s *SubnetSuite) TestSubnetRemoveKillsAddresses(c *gc.C) {
	subnetInfo := state.SubnetInfo{CIDR: "192.168.1.0/24"}
	subnet, err := s.State.AddSubnet(subnetInfo)
	c.Assert(err, jc.ErrorIsNil)

	_, err = s.State.AddIPAddress(network.NewAddress("192.168.1.0", ""), subnet.ID())
	c.Assert(err, jc.ErrorIsNil)
	_, err = s.State.AddIPAddress(network.NewAddress("192.168.1.1", ""), subnet.ID())
	c.Assert(err, jc.ErrorIsNil)

	err = subnet.EnsureDead()
	c.Assert(err, jc.ErrorIsNil)
	err = subnet.Remove()
	c.Assert(err, jc.ErrorIsNil)

	_, err = s.State.IPAddress("192.168.1.0")
	c.Assert(errors.Cause(err), jc.Satisfies, errors.IsNotFound)
	_, err = s.State.IPAddress("192.168.1.1")
	c.Assert(errors.Cause(err), jc.Satisfies, errors.IsNotFound)
}

func (s *SubnetSuite) TestRefresh(c *gc.C) {
	subnetInfo := state.SubnetInfo{CIDR: "192.168.1.0/24"}

	subnet, err := s.State.AddSubnet(subnetInfo)
	c.Assert(err, jc.ErrorIsNil)

	subnetCopy, err := s.State.Subnet("192.168.1.0/24")
	c.Assert(err, jc.ErrorIsNil)

	err = subnet.EnsureDead()
	c.Assert(err, jc.ErrorIsNil)

	c.Assert(subnetCopy.Life(), gc.Equals, state.Alive)
	err = subnetCopy.Refresh()
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(subnetCopy.Life(), gc.Equals, state.Dead)
}

func (s *SubnetSuite) TestPickNewAddressNoAddresses(c *gc.C) {
	subnetInfo := state.SubnetInfo{
		CIDR:              "192.168.1.0/24",
		AllocatableIPLow:  "",
		AllocatableIPHigh: "",
	}
	subnet, err := s.State.AddSubnet(subnetInfo)
	c.Assert(err, jc.ErrorIsNil)

	_, err = subnet.PickNewAddress()
	c.Assert(err, gc.ErrorMatches, "no allocatable IP addresses for subnet .*")
}

func (s *SubnetSuite) getSubnetForAddressPicking(c *gc.C, allocatableHigh string) *state.Subnet {
	subnetInfo := state.SubnetInfo{
		CIDR:              "192.168.1.0/24",
		AllocatableIPLow:  "192.168.1.0",
		AllocatableIPHigh: allocatableHigh,
	}
	subnet, err := s.State.AddSubnet(subnetInfo)
	c.Assert(err, jc.ErrorIsNil)
	return subnet
}

func (s *SubnetSuite) TestPickNewAddressAddressesExhausted(c *gc.C) {
	subnet := s.getSubnetForAddressPicking(c, "192.168.1.0")
	addr := network.NewAddress("192.168.1.0", network.ScopeUnknown)
	_, err := s.State.AddIPAddress(addr, subnet.ID())

	_, err = subnet.PickNewAddress()
	c.Assert(err, gc.ErrorMatches, "allocatable IP addresses exhausted for subnet .*")
}

func (s *SubnetSuite) TestPickNewAddressOneAddress(c *gc.C) {
	subnet := s.getSubnetForAddressPicking(c, "192.168.1.0")

	addr, err := subnet.PickNewAddress()
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(addr.Value(), gc.Equals, "192.168.1.0")
}

func (s *SubnetSuite) TestPickNewAddressSkipsAllocated(c *gc.C) {
	subnet := s.getSubnetForAddressPicking(c, "192.168.1.1")

	addr := network.NewAddress("192.168.1.0", network.ScopeUnknown)
	_, err := s.State.AddIPAddress(addr, subnet.ID())

	ipAddr, err := subnet.PickNewAddress()
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(ipAddr.Value(), gc.Equals, "192.168.1.1")
}

func (s *SubnetSuite) TestPickNewAddressRace(c *gc.C) {
	// represents 192.168.1.0
	initialIP := uint32(3232235776)
	var index int32 = -1
	addresses := []uint32{initialIP, initialIP, initialIP + 1}

	// the first two calls will get the same address (which simulates the
	// inherent race condition in the code). The third call will get
	// a new one. We should see two different addresses come out of the
	// two calls: i.e. we will have detected the race condition and tried
	// again.
	mockPickAddress := func(_, _ uint32, _ map[uint32]bool) uint32 {
		theIndex := atomic.AddInt32(&index, 1)
		return addresses[theIndex]
	}
	s.PatchValue(&state.PickAddress, &mockPickAddress)

	// 192.168.1.0 and 192.168.1.1 are the only valid addresses
	subnet := s.getSubnetForAddressPicking(c, "192.168.1.1")

	waiter := sync.WaitGroup{}
	waiter.Add(2)

	var firstResult *state.IPAddress
	var firstError error
	var secondResult *state.IPAddress
	var secondError error
	go func() {
		firstResult, firstError = subnet.PickNewAddress()
		waiter.Done()
	}()
	go func() {
		secondResult, secondError = subnet.PickNewAddress()
		waiter.Done()
	}()
	waiter.Wait()

	c.Assert(firstError, jc.ErrorIsNil)
	c.Assert(secondError, jc.ErrorIsNil)
	c.Assert(firstResult, gc.NotNil)
	c.Assert(secondResult, gc.NotNil)

	ipAddresses := []string{firstResult.Value(), secondResult.Value()}
	sort.Strings(ipAddresses)

	expected := []string{"192.168.1.0", "192.168.1.1"}
	c.Assert(ipAddresses, jc.DeepEquals, expected)
}
<|endoftext|>


<commit_before>package aci

/*

Image Layout

The on-disk layout of an app container is straightforward.
It includes a rootfs with all of the files that will exist in the root of the app and a manifest describing the image.
The layout must contain an app image manifest.

/manifest
/rootfs/
/rootfs/usr/bin/mysql

*/

import (
	"archive/tar"
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/coreos/rocket/app-container/schema"
)

var (
	ErrNoRootFS   = errors.New("no rootfs found in layout")
	ErrNoManifest = errors.New("no app image manifest found in layout")
)

// ValidateLayout takes a directory and validates that the layout of the directory
// matches that expected by the Application Container Image format.
// If any errors are encountered during the validation, it will abort and
// return the first one.
func ValidateLayout(dir string) error {
	fi, err := os.Stat(dir)
	if err != nil {
		return fmt.Errorf("error accessing layout: %v", err)
	}
	if !fi.IsDir() {
		return fmt.Errorf("given path %q is not a directory", dir)
	}
	var flist []string
	var amOK, rfsOK bool
	var am io.Reader
	walkLayout := func(fpath string, fi os.FileInfo, err error) error {
		rpath := strings.TrimPrefix(fpath, dir)
		name := filepath.Base(rpath)
		switch name {
		case ".":
		case "app":
			am, err = os.Open(fpath)
			if err != nil {
				return err
			}
			amOK = true
		case "rootfs":
			if !fi.IsDir() {
				return errors.New("rootfs is not a directory")
			}
			rfsOK = true
		default:
			flist = append(flist, rpath)
		}
		return nil
	}
	if err := filepath.Walk(dir, walkLayout); err != nil {
		return err
	}
	return validate(amOK, am, rfsOK, flist)
}

// ValidateLayout takes a *tar.Reader and validates that the layout of the
// filesystem the reader encapsulates matches that expected by the
// Application Container Image format.  If any errors are encountered during
// the validation, it will abort and return the first one.
func ValidateArchive(tr *tar.Reader) error {
	var flist []string
	var amOK, rfsOK bool
	var am bytes.Buffer
Tar:
	for {
		hdr, err := tr.Next()
		switch {
		case err == nil:
		case err == io.EOF:
			break Tar
		default:
			return err
		}
		switch hdr.Name {
		case "app":
			_, err := io.Copy(&am, tr)
			if err != nil {
				return err
			}
			amOK = true
		case "rootfs/":
			if !hdr.FileInfo().IsDir() {
				return fmt.Errorf("rootfs is not a directory")
			}
			rfsOK = true
		default:
			flist = append(flist, hdr.Name)
		}
	}
	return validate(amOK, &am, rfsOK, flist)
}

func validate(amOK bool, am io.Reader, rfsOK bool, files []string) error {
	if !amOK {
		return ErrNoManifest
	}
	if amOK {
		if !rfsOK {
			return ErrNoRootFS
		}
		b, err := ioutil.ReadAll(am)
		if err != nil {
			return fmt.Errorf("error reading app manifest: %v", err)
		}
		var a schema.ImageManifest
		if err := a.UnmarshalJSON(b); err != nil {
			return fmt.Errorf("app manifest validation failed: %v", err)
		}
	}
	for _, f := range files {
		if !strings.HasPrefix(f, "rootfs") {
			return fmt.Errorf("unrecognized file path in layout: %q", f)
		}
	}
	return nil
}

// validateImageManifest ensures that the given io.Reader represents a valid
// ImageManifest.
func validateImageManifest(r io.Reader) error {
	b, err := ioutil.ReadAll(r)
	if err != nil {
		return fmt.Errorf("error reading app manifest: %v", err)
	}
	var am schema.ImageManifest
	if err = json.Unmarshal(b, &am); err != nil {
		return fmt.Errorf("error unmarshaling app manifest: %v", err)
	}
	return nil
}
<commit_msg>appc: fix relative paths in app validator<commit_after>package aci

/*

Image Layout

The on-disk layout of an app container is straightforward.
It includes a rootfs with all of the files that will exist in the root of the app and a manifest describing the image.
The layout must contain an app image manifest.

/manifest
/rootfs/
/rootfs/usr/bin/mysql

*/

import (
	"archive/tar"
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/coreos/rocket/app-container/schema"
)

var (
	ErrNoRootFS   = errors.New("no rootfs found in layout")
	ErrNoManifest = errors.New("no app image manifest found in layout")
)

// ValidateLayout takes a directory and validates that the layout of the directory
// matches that expected by the Application Container Image format.
// If any errors are encountered during the validation, it will abort and
// return the first one.
func ValidateLayout(dir string) error {
	fi, err := os.Stat(dir)
	if err != nil {
		return fmt.Errorf("error accessing layout: %v", err)
	}
	if !fi.IsDir() {
		return fmt.Errorf("given path %q is not a directory", dir)
	}
	var flist []string
	var imOK, rfsOK bool
	var im io.Reader
	walkLayout := func(fpath string, fi os.FileInfo, err error) error {
		rpath, err := filepath.Rel(dir, fpath)
		if err != nil {
			return err
		}
		name := filepath.Base(rpath)
		switch name {
		case ".":
		case "app":
			im, err = os.Open(fpath)
			if err != nil {
				return err
			}
			imOK = true
		case "rootfs":
			if !fi.IsDir() {
				return errors.New("rootfs is not a directory")
			}
			rfsOK = true
		default:
			flist = append(flist, rpath)
		}
		return nil
	}
	if err := filepath.Walk(dir, walkLayout); err != nil {
		return err
	}
	return validate(imOK, im, rfsOK, flist)
}

// ValidateLayout takes a *tar.Reader and validates that the layout of the
// filesystem the reader encapsulates matches that expected by the
// Application Container Image format.  If any errors are encountered during
// the validation, it will abort and return the first one.
func ValidateArchive(tr *tar.Reader) error {
	var flist []string
	var imOK, rfsOK bool
	var im bytes.Buffer
Tar:
	for {
		hdr, err := tr.Next()
		switch {
		case err == nil:
		case err == io.EOF:
			break Tar
		default:
			return err
		}
		switch hdr.Name {
		case "app":
			_, err := io.Copy(&im, tr)
			if err != nil {
				return err
			}
			imOK = true
		case "rootfs/":
			if !hdr.FileInfo().IsDir() {
				return fmt.Errorf("rootfs is not a directory")
			}
			rfsOK = true
		default:
			flist = append(flist, hdr.Name)
		}
	}
	return validate(imOK, &im, rfsOK, flist)
}

func validate(imOK bool, im io.Reader, rfsOK bool, files []string) error {
	if !imOK {
		return ErrNoManifest
	}
	if !rfsOK {
		return ErrNoRootFS
	}
	b, err := ioutil.ReadAll(im)
	if err != nil {
		return fmt.Errorf("error reading app manifest: %v", err)
	}
	var a schema.ImageManifest
	if err := a.UnmarshalJSON(b); err != nil {
		return fmt.Errorf("app manifest validation failed: %v", err)
	}
	for _, f := range files {
		if !strings.HasPrefix(f, "rootfs") {
			return fmt.Errorf("unrecognized file path in layout: %q", f)
		}
	}
	return nil
}

// validateImageManifest ensures that the given io.Reader represents a valid
// ImageManifest.
func validateImageManifest(r io.Reader) error {
	b, err := ioutil.ReadAll(r)
	if err != nil {
		return fmt.Errorf("error reading app manifest: %v", err)
	}
	var im schema.ImageManifest
	if err = json.Unmarshal(b, &im); err != nil {
		return fmt.Errorf("error unmarshaling app manifest: %v", err)
	}
	return nil
}
<|endoftext|>


<commit_before>package tail

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"testing"
	"time"
)

const (
	RotateMarker   = "__ROTATE__\n"
	TruncateMarker = "__TRUNCATE__\n"
	EOFMarker      = "__EOF__\n"
)

var Logs = []string{
	"single line\n",
	"multi line 1\nmulti line 2\nmulti line 3\n",
	"continuous line 1", "continuous line 2", "continuous line 3\n",
	RotateMarker,
	"foo\n",
	"bar\n",
	"baz\n",
	TruncateMarker,
	"FOOOO\n",
	"BAAAR\n",
	"BAZZZZZZZ\n",
	EOFMarker,
}

func TestTailFile(t *testing.T) {
	tmpdir, err := ioutil.TempDir("", "go-tail.")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer os.RemoveAll(tmpdir)

	go writeFile(t, tmpdir)
	tail, err := NewTailFile(filepath.Join(tmpdir, "test.log"))
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer tail.Close()

	expected := strings.Join(Logs, "")
	actual, err := receive(t, tail)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if actual != expected {
		t.Errorf("got %s\nwant %s", actual, expected)
	}
}

func TestTailReader(t *testing.T) {
	reader, writer := io.Pipe()

	go writeWriter(t, writer)
	tail, err := NewTailReader(reader)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	expected := strings.Join(Logs, "")
	actual, err := receive(t, tail)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if actual != expected {
		t.Errorf("got %s\nwant %s", actual, expected)
	}

	reader.Close()
	writer.Close()
	select {
	case _, ok := <-tail.Lines:
		if ok {
			t.Error("want closed, but not")
		}
	case <-time.After(time.Second):
		t.Error("want closed, but not")
	}
	select {
	case _, ok := <-tail.Errors:
		if ok {
			t.Error("want closed, but not")
		}
	case <-time.After(time.Second):
		t.Error("want closed, but not")
	}
	tail.Close()
}

func writeFile(t *testing.T, tmpdir string) error {
	filename := filepath.Join(tmpdir, "test.log")
	file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return err
	}

	// wait for starting to tail...
	time.Sleep(2 * time.Second)

	for _, line := range Logs {
		_, err := file.WriteString(line)
		if err != nil {
			return err
		}
		t.Logf("write: %s", line)
		switch line {
		case RotateMarker:
			if err := file.Close(); err != nil {
				return err
			}
			if err := os.Rename(filename, filename+".old"); err != nil {
				return err
			}
			file, err = os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0644)
			if err != nil {
				return err
			}
		case TruncateMarker:
			time.Sleep(1 * time.Second)
			if _, err := file.Seek(0, os.SEEK_SET); err != nil {
				return err
			}
			if err := os.Truncate(filename, 0); err != nil {
				return err
			}
		}
		time.Sleep(90 * time.Millisecond)
	}

	if err := file.Close(); err != nil {
		return err
	}
	return nil
}

func writeWriter(t *testing.T, writer io.Writer) error {
	w := bufio.NewWriter(writer)
	for _, line := range Logs {
		_, err := w.WriteString(line)
		if err != nil {
			return err
		}
		if err := w.Flush(); err != nil {
			return err
		}
		t.Logf("write: %s", line)
		time.Sleep(90 * time.Millisecond)
	}
	return nil
}

func receive(t *testing.T, tail *Tail) (string, error) {
	actual := ""
	for {
		select {
		case line := <-tail.Lines:
			t.Logf("received: %s", line.Text)
			actual += line.Text
			if line.Text == EOFMarker {
				return actual, nil
			}
		case err := <-tail.Errors:
			return "", err
		case <-time.After(5 * time.Second):
			return "", errors.New("timeout")
		}
	}
}

func TestTailFile_Rotate(t *testing.T) {
	tmpdir, err := ioutil.TempDir("", "go-tail.")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer os.RemoveAll(tmpdir)

	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		filename := filepath.Join(tmpdir, "test.log")
		for i := 0; i < 10; i++ {
			file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0644)
			if err != nil {
				t.Error(err)
				return
			}
			if i == 0 {
				// wait for starting to tail...
				time.Sleep(2 * time.Second)
			}

			// start to write logs
			wg.Add(1)
			go func() {
				defer wg.Done()
				writeFileAndClose(t, file, fmt.Sprintf("file: %d\n", i))
			}()
			time.Sleep(time.Second)

			// Rotate log file, and start writing logs into a new file.
			// While, some logs are still written into the old file.
			os.Rename(filename, fmt.Sprintf("%s.%d", filename, i))
		}
	}()

	tail, err := NewTailFile(filepath.Join(tmpdir, "test.log"))
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	go func() {
		wg.Wait()
		tail.Close()
	}()
	go func() {
		for err := range tail.Errors {
			t.Log("error: ", err)
		}
	}()

	var cnt int
	for range tail.Lines {
		cnt++
	}
	if cnt != 1000 {
		t.Errorf("want 1000, got %d", cnt)
	}
}

func writeFileAndClose(t *testing.T, file *os.File, line string) {
	for i := 0; i < 100; i++ {
		_, err := file.WriteString(line)
		if err != nil {
			_ = file.Close()
			t.Error(err)
			return
		}
		time.Sleep(90 * time.Millisecond)
	}

	if err := file.Close(); err != nil {
		t.Error(err)
	}
}
<commit_msg>fix data race<commit_after>package tail

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"testing"
	"time"
)

const (
	RotateMarker   = "__ROTATE__\n"
	TruncateMarker = "__TRUNCATE__\n"
	EOFMarker      = "__EOF__\n"
)

var Logs = []string{
	"single line\n",
	"multi line 1\nmulti line 2\nmulti line 3\n",
	"continuous line 1", "continuous line 2", "continuous line 3\n",
	RotateMarker,
	"foo\n",
	"bar\n",
	"baz\n",
	TruncateMarker,
	"FOOOO\n",
	"BAAAR\n",
	"BAZZZZZZZ\n",
	EOFMarker,
}

func TestTailFile(t *testing.T) {
	tmpdir, err := ioutil.TempDir("", "go-tail.")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer os.RemoveAll(tmpdir)

	go writeFile(t, tmpdir)
	tail, err := NewTailFile(filepath.Join(tmpdir, "test.log"))
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer tail.Close()

	expected := strings.Join(Logs, "")
	actual, err := receive(t, tail)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if actual != expected {
		t.Errorf("got %s\nwant %s", actual, expected)
	}
}

func TestTailReader(t *testing.T) {
	reader, writer := io.Pipe()

	go writeWriter(t, writer)
	tail, err := NewTailReader(reader)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	expected := strings.Join(Logs, "")
	actual, err := receive(t, tail)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if actual != expected {
		t.Errorf("got %s\nwant %s", actual, expected)
	}

	reader.Close()
	writer.Close()
	select {
	case _, ok := <-tail.Lines:
		if ok {
			t.Error("want closed, but not")
		}
	case <-time.After(time.Second):
		t.Error("want closed, but not")
	}
	select {
	case _, ok := <-tail.Errors:
		if ok {
			t.Error("want closed, but not")
		}
	case <-time.After(time.Second):
		t.Error("want closed, but not")
	}
	tail.Close()
}

func writeFile(t *testing.T, tmpdir string) error {
	filename := filepath.Join(tmpdir, "test.log")
	file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return err
	}

	// wait for starting to tail...
	time.Sleep(2 * time.Second)

	for _, line := range Logs {
		_, err := file.WriteString(line)
		if err != nil {
			return err
		}
		t.Logf("write: %s", line)
		switch line {
		case RotateMarker:
			if err := file.Close(); err != nil {
				return err
			}
			if err := os.Rename(filename, filename+".old"); err != nil {
				return err
			}
			file, err = os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0644)
			if err != nil {
				return err
			}
		case TruncateMarker:
			time.Sleep(1 * time.Second)
			if _, err := file.Seek(0, os.SEEK_SET); err != nil {
				return err
			}
			if err := os.Truncate(filename, 0); err != nil {
				return err
			}
		}
		time.Sleep(90 * time.Millisecond)
	}

	if err := file.Close(); err != nil {
		return err
	}
	return nil
}

func writeWriter(t *testing.T, writer io.Writer) error {
	w := bufio.NewWriter(writer)
	for _, line := range Logs {
		_, err := w.WriteString(line)
		if err != nil {
			return err
		}
		if err := w.Flush(); err != nil {
			return err
		}
		t.Logf("write: %s", line)
		time.Sleep(90 * time.Millisecond)
	}
	return nil
}

func receive(t *testing.T, tail *Tail) (string, error) {
	actual := ""
	for {
		select {
		case line := <-tail.Lines:
			t.Logf("received: %s", line.Text)
			actual += line.Text
			if line.Text == EOFMarker {
				return actual, nil
			}
		case err := <-tail.Errors:
			return "", err
		case <-time.After(5 * time.Second):
			return "", errors.New("timeout")
		}
	}
}

func TestTailFile_Rotate(t *testing.T) {
	tmpdir, err := ioutil.TempDir("", "go-tail.")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer os.RemoveAll(tmpdir)

	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		filename := filepath.Join(tmpdir, "test.log")
		for i := 0; i < 10; i++ {
			i := i
			file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0644)
			if err != nil {
				t.Error(err)
				return
			}
			if i == 0 {
				// wait for starting to tail...
				time.Sleep(2 * time.Second)
			}

			// start to write logs
			wg.Add(1)
			go func() {
				defer wg.Done()
				writeFileAndClose(t, file, fmt.Sprintf("file: %d\n", i))
			}()
			time.Sleep(time.Second)

			// Rotate log file, and start writing logs into a new file.
			// While, some logs are still written into the old file.
			os.Rename(filename, fmt.Sprintf("%s.%d", filename, i))
		}
	}()

	tail, err := NewTailFile(filepath.Join(tmpdir, "test.log"))
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	go func() {
		wg.Wait()
		tail.Close()
	}()
	go func() {
		for err := range tail.Errors {
			t.Log("error: ", err)
		}
	}()

	var cnt int
	for range tail.Lines {
		cnt++
	}
	if cnt != 1000 {
		t.Errorf("want 1000, got %d", cnt)
	}
}

func writeFileAndClose(t *testing.T, file *os.File, line string) {
	for i := 0; i < 100; i++ {
		_, err := file.WriteString(line)
		if err != nil {
			_ = file.Close()
			t.Error(err)
			return
		}
		time.Sleep(90 * time.Millisecond)
	}

	if err := file.Close(); err != nil {
		t.Error(err)
	}
}
<|endoftext|>


<commit_before>// Copyright 2011 Google Inc. All Rights Reserved.
// This file is available under the Apache license.

package main

import (
	"io/ioutil"
	"os"
	"testing"
)

func TestTail(t *testing.T) {
	d, err := ioutil.TempDir("", "tail_test")
	if err != nil {
		t.Error(err)
	}
	defer os.RemoveAll(d)

	logfile := d + "/log"
	f, err := os.Create(logfile)
	if err != nil {
		t.Error(err)
	}
	defer f.Close()

	lines := make(chan string)
	ta := NewTailer(lines)
	if ta == nil {
		t.Fatalf("Couldn't make a tailer.")
	}
	ta.Tail(logfile)

	if _, ok := ta.files[logfile]; !ok {
		t.Error("path not found in files map")
	}
}

func TestHandleLogChange(t *testing.T) {
	d, err := ioutil.TempDir("", "tail_test")
	if err != nil {
		t.Error(err)
	}
	defer os.RemoveAll(d)

	logfile := d + "/log"
	f, err := os.Create(logfile)
	if err != nil {
		t.Error(err)
	}
	defer f.Close()

	lines := make(chan string)
	ta := NewTailer(lines)
	if ta == nil {
		t.Fatalf("Couldn't make a tailer.")
	}
	ta.Tail(logfile)

	_, err = f.WriteString("a\nb\nc\nd\n")
	if err != nil {
		t.Error(err)
	}
	go ta.handleLogUpdate(logfile)

	for _, expected := range []string{"a", "b", "c", "d"} {
		// Run as a goroutine because it's going to emit lines via output channel
		line := <-ta.lines
		if line != expected {
			t.Errorf("line doesn't match:\n\texpected: %s\n\tgot: %s", expected, line)
			continue
		}
	}
}

func TestHandleLogChangePartialLine(t *testing.T) {
	d, err := ioutil.TempDir("", "tail_test")
	if err != nil {
		t.Error(err)
	}
	defer os.RemoveAll(d)

	logfile := d + "/log"
	f, err := os.Create(logfile)
	if err != nil {
		t.Error(err)
	}
	defer f.Close()

	lines := make(chan string)
	ta := NewTailer(lines)
	if ta == nil {
		t.Fatalf("Couldn't make a tailer.")
	}
	ta.Tail(logfile)

	_, err = f.WriteString("a")
	if err != nil {
		t.Error(err)
	}
	go ta.handleLogUpdate(logfile)
	select {
	case line := <-ta.lines:
		t.Errorf("unexpected line found: %s", line)
	default:
	}

	_, err = f.WriteString("b")
	if err != nil {
		t.Error(err)
	}
	go ta.handleLogUpdate(logfile)

	select {
	case line := <-ta.lines:
		t.Errorf("unexpected line found: %s", line)
	default:
	}

	_, err = f.WriteString("\n")
	if err != nil {
		t.Error(err)
	}
	go ta.handleLogUpdate(logfile)
	line := <-ta.lines
	if line != "ab" {
		t.Errorf("line doesn't match: expected 'ab' vs %s", line)
	}
}
<commit_msg>Move tail tests out of 'short'.<commit_after>// Copyright 2011 Google Inc. All Rights Reserved.
// This file is available under the Apache license.

package main

import (
	"io/ioutil"
	"os"
	"testing"
)

func TestTail(t *testing.T) {
	if testing.Short() {
		return
	}
	d, err := ioutil.TempDir("", "tail_test")
	if err != nil {
		t.Error(err)
	}
	defer os.RemoveAll(d)

	logfile := d + "/log"
	f, err := os.Create(logfile)
	if err != nil {
		t.Error(err)
	}
	defer f.Close()

	lines := make(chan string)
	ta := NewTailer(lines)
	if ta == nil {
		t.Fatalf("Couldn't make a tailer.")
	}
	ta.Tail(logfile)

	if _, ok := ta.files[logfile]; !ok {
		t.Error("path not found in files map")
	}
}

func TestHandleLogChange(t *testing.T) {
	if testing.Short() {
		return
	}
	d, err := ioutil.TempDir("", "tail_test")
	if err != nil {
		t.Error(err)
	}
	defer os.RemoveAll(d)

	logfile := d + "/log"
	f, err := os.Create(logfile)
	if err != nil {
		t.Error(err)
	}
	defer f.Close()

	lines := make(chan string)
	ta := NewTailer(lines)
	if ta == nil {
		t.Fatalf("Couldn't make a tailer.")
	}
	ta.Tail(logfile)

	_, err = f.WriteString("a\nb\nc\nd\n")
	if err != nil {
		t.Error(err)
	}
	go ta.handleLogUpdate(logfile)

	for _, expected := range []string{"a", "b", "c", "d"} {
		// Run as a goroutine because it's going to emit lines via output channel
		line := <-ta.lines
		if line != expected {
			t.Errorf("line doesn't match:\n\texpected: %s\n\tgot: %s", expected, line)
			continue
		}
	}
}

func TestHandleLogChangePartialLine(t *testing.T) {
	if testing.Short() {
		return
	}
	d, err := ioutil.TempDir("", "tail_test")
	if err != nil {
		t.Error(err)
	}
	defer os.RemoveAll(d)

	logfile := d + "/log"
	f, err := os.Create(logfile)
	if err != nil {
		t.Error(err)
	}
	defer f.Close()

	lines := make(chan string)
	ta := NewTailer(lines)
	if ta == nil {
		t.Fatalf("Couldn't make a tailer.")
	}
	ta.Tail(logfile)

	_, err = f.WriteString("a")
	if err != nil {
		t.Error(err)
	}
	go ta.handleLogUpdate(logfile)
	select {
	case line := <-ta.lines:
		t.Errorf("unexpected line found: %s", line)
	default:
	}

	_, err = f.WriteString("b")
	if err != nil {
		t.Error(err)
	}
	go ta.handleLogUpdate(logfile)

	select {
	case line := <-ta.lines:
		t.Errorf("unexpected line found: %s", line)
	default:
	}

	_, err = f.WriteString("\n")
	if err != nil {
		t.Error(err)
	}
	go ta.handleLogUpdate(logfile)
	line := <-ta.lines
	if line != "ab" {
		t.Errorf("line doesn't match: expected 'ab' vs %s", line)
	}
}
<|endoftext|>


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

// TODO(rsc): All the prints in this file should go to standard error.

package net

import (
	"net";
	"once";
	"os";
	"sync";
	"syscall";
)

// Network file descriptor.
type netFD struct {
	// immutable until Close
	fd int64;
	file *os.File;
	cr chan *netFD;
	cw chan *netFD;
	net string;
	laddr string;
	raddr string;

	// owned by client
	rdeadline_delta int64;
	rdeadline int64;
	rio sync.Mutex;
	wdeadline_delta int64;
	wdeadline int64;
	wio sync.Mutex;

	// owned by fd wait server
	ncr, ncw int;
}

// Make reads and writes on fd return EAGAIN instead of blocking.
func setNonblock(fd int64) *os.Error {
	flags, e := syscall.Fcntl(fd, syscall.F_GETFL, 0);
	if e != 0 {
		return os.ErrnoToError(e)
	}
	flags, e = syscall.Fcntl(fd, syscall.F_SETFL, flags | syscall.O_NONBLOCK);
	if e != 0 {
		return os.ErrnoToError(e)
	}
	return nil
}

// Make reads/writes blocking; last gasp, so no error checking.
func setBlock(fd int64) {
	flags, e := syscall.Fcntl(fd, syscall.F_GETFL, 0);
	if e != 0 {
		return;
	}
	syscall.Fcntl(fd, syscall.F_SETFL, flags & ^syscall.O_NONBLOCK);
}

// A pollServer helps FDs determine when to retry a non-blocking
// read or write after they get EAGAIN.  When an FD needs to wait,
// send the fd on s.cr (for a read) or s.cw (for a write) to pass the
// request to the poll server.  Then receive on fd.cr/fd.cw.
// When the pollServer finds that i/o on FD should be possible
// again, it will send fd on fd.cr/fd.cw to wake any waiting processes.
// This protocol is implemented as s.WaitRead() and s.WaitWrite().
//
// There is one subtlety: when sending on s.cr/s.cw, the
// poll server is probably in a system call, waiting for an fd
// to become ready.  It's not looking at the request channels.
// To resolve this, the poll server waits not just on the FDs it has
// been given but also its own pipe.  After sending on the
// buffered channel s.cr/s.cw, WaitRead/WaitWrite writes a
// byte to the pipe, causing the pollServer's poll system call to
// return.  In response to the pipe being readable, the pollServer
// re-polls its request channels.
//
// Note that the ordering is "send request" and then "wake up server".
// If the operations were reversed, there would be a race: the poll
// server might wake up and look at the request channel, see that it
// was empty, and go back to sleep, all before the requester managed
// to send the request.  Because the send must complete before the wakeup,
// the request channel must be buffered.  A buffer of size 1 is sufficient
// for any request load.  If many processes are trying to submit requests,
// one will succeed, the pollServer will read the request, and then the
// channel will be empty for the next process's request.  A larger buffer
// might help batch requests.

type pollServer struct {
	cr, cw chan *netFD;	// buffered >= 1
	pr, pw *os.File;
	pending map[int64] *netFD;
	poll *pollster;	// low-level OS hooks
	deadline int64;	// next deadline (nsec since 1970)
}
func (s *pollServer) Run();

func newPollServer() (s *pollServer, err *os.Error) {
	s = new(pollServer);
	s.cr = make(chan *netFD, 1);
	s.cw = make(chan *netFD, 1);
	if s.pr, s.pw, err = os.Pipe(); err != nil {
		return nil, err
	}
	if err = setNonblock(s.pr.Fd()); err != nil {
	Error:
		s.pr.Close();
		s.pw.Close();
		return nil, err
	}
	if err = setNonblock(s.pw.Fd()); err != nil {
		goto Error
	}
	if s.poll, err = newpollster(); err != nil {
		goto Error
	}
	if err = s.poll.AddFD(s.pr.Fd(), 'r', true); err != nil {
		s.poll.Close();
		goto Error
	}
	s.pending = make(map[int64] *netFD);
	go s.Run();
	return s, nil
}

func (s *pollServer) AddFD(fd *netFD, mode int) {
	if err := s.poll.AddFD(fd.fd, mode, false); err != nil {
		panicln("pollServer AddFD ", fd.fd, ": ", err.String(), "\n");
		return
	}

	var t int64;
	key := fd.fd << 1;
	if mode == 'r' {
		fd.ncr++;
		t = fd.rdeadline;
	} else {
		fd.ncw++;
		key++;
		t = fd.wdeadline;
	}
	s.pending[key] = fd;
	if t > 0 && (s.deadline == 0 || t < s.deadline) {
		s.deadline = t;
	}
}

func (s *pollServer) LookupFD(fd int64, mode int) *netFD {
	key := fd << 1;
	if mode == 'w' {
		key++;
	}
	netfd, ok := s.pending[key];
	if !ok {
		return nil
	}
	s.pending[key] = nil, false;
	return netfd
}

func (s *pollServer) WakeFD(fd *netFD, mode int) {
	if mode == 'r' {
		for fd.ncr > 0 {
			fd.ncr--;
			fd.cr <- fd
		}
	} else {
		for fd.ncw > 0 {
			fd.ncw--;
			fd.cw <- fd
		}
	}
}

func (s *pollServer) Now() int64 {
	sec, nsec, err := os.Time();
	if err != nil {
		panic("net: os.Time: ", err.String());
	}
	nsec += sec * 1e9;
	return nsec;
}

func (s *pollServer) CheckDeadlines() {
	now := s.Now();
	// TODO(rsc): This will need to be handled more efficiently,
	// probably with a heap indexed by wakeup time.

	var next_deadline int64;
	for key, fd := range s.pending {
		var t int64;
		var mode int;
		if key&1 == 0 {
			mode = 'r';
		} else {
			mode = 'w';
		}
		if mode == 'r' {
			t = fd.rdeadline;
		} else {
			t = fd.wdeadline;
		}
		if t > 0 {
			if t <= now {
				s.pending[key] = nil, false;
				if mode == 'r' {
					s.poll.DelFD(fd.fd, mode);
					fd.rdeadline = -1;
				} else {
					s.poll.DelFD(fd.fd, mode);
					fd.wdeadline = -1;
				}
				s.WakeFD(fd, mode);
			} else if next_deadline == 0 || t < next_deadline {
				next_deadline = t;
			}
		}
	}
	s.deadline = next_deadline;
}

func (s *pollServer) Run() {
	var scratch [100]byte;
	for {
		var t = s.deadline;
		if t > 0 {
			t = t - s.Now();
			if t < 0 {
				s.CheckDeadlines();
				continue;
			}
		}
		fd, mode, err := s.poll.WaitFD(t);
		if err != nil {
			print("pollServer WaitFD: ", err.String(), "\n");
			return
		}
		if fd < 0 {
			// Timeout happened.
			s.CheckDeadlines();
			continue;
		}
		if fd == s.pr.Fd() {
			// Drain our wakeup pipe.
			for nn, e := s.pr.Read(scratch); nn > 0; {
				nn, e = s.pr.Read(scratch)
			}

			// Read from channels
			for fd, ok := <-s.cr; ok; fd, ok = <-s.cr {
				s.AddFD(fd, 'r')
			}
			for fd, ok := <-s.cw; ok; fd, ok = <-s.cw {
				s.AddFD(fd, 'w')
			}
		} else {
			netfd := s.LookupFD(fd, mode);
			if netfd == nil {
				print("pollServer: unexpected wakeup for fd=", netfd, " mode=", string(mode), "\n");
				continue
			}
			s.WakeFD(netfd, mode);
		}
	}
}

func (s *pollServer) Wakeup() {
	var b [1]byte;
	s.pw.Write(b)
}

func (s *pollServer) WaitRead(fd *netFD) {
	s.cr <- fd;
	s.Wakeup();
	<-fd.cr
}

func (s *pollServer) WaitWrite(fd *netFD) {
	s.cr <- fd;
	s.Wakeup();
	<-fd.cr
}

// Network FD methods.
// All the network FDs use a single pollServer.

var pollserver *pollServer

func _StartServer() {
	p, err := newPollServer();
	if err != nil {
		print("Start pollServer: ", err.String(), "\n")
	}
	pollserver = p
}

func newFD(fd int64, net, laddr, raddr string) (f *netFD, err *os.Error) {
	if pollserver == nil {
		once.Do(_StartServer);
	}
	if err = setNonblock(fd); err != nil {
		return nil, err
	}
	f = new(netFD);
	f.fd = fd;
	f.net = net;
	f.laddr = laddr;
	f.raddr = raddr;
	f.file = os.NewFile(fd, "net: " + net + " " + laddr + " " + raddr);
	f.cr = make(chan *netFD, 1);
	f.cw = make(chan *netFD, 1);
	return f, nil
}

func (fd *netFD) Close() *os.Error {
	if fd == nil || fd.file == nil {
		return os.EINVAL
	}

	// In case the user has set linger,
	// switch to blocking mode so the close blocks.
	// As long as this doesn't happen often,
	// we can handle the extra OS processes.
	// Otherwise we'll need to use the pollserver
	// for Close too.  Sigh.
	setBlock(fd.file.Fd());

	e := fd.file.Close();
	fd.file = nil;
	fd.fd = -1;
	return e
}

func (fd *netFD) Read(p []byte) (n int, err *os.Error) {
	if fd == nil || fd.file == nil {
		return -1, os.EINVAL
	}
	fd.rio.Lock();
	defer fd.rio.Unlock();
	if fd.rdeadline_delta > 0 {
		fd.rdeadline = pollserver.Now() + fd.rdeadline_delta;
	} else {
		fd.rdeadline = 0;
	}
	n, err = fd.file.Read(p);
	for err == os.EAGAIN && fd.rdeadline >= 0 {
		pollserver.WaitRead(fd);
		n, err = fd.file.Read(p)
	}
	return n, err
}

func (fd *netFD) Write(p []byte) (n int, err *os.Error) {
	if fd == nil || fd.file == nil {
		return -1, os.EINVAL
	}
	fd.wio.Lock();
	defer fd.wio.Unlock();
	if fd.wdeadline_delta > 0 {
		fd.wdeadline = pollserver.Now() + fd.wdeadline_delta;
	} else {
		fd.wdeadline = 0;
	}
	err = nil;
	nn := 0;
	for nn < len(p) {
		n, err = fd.file.Write(p[nn:len(p)]);
		if n > 0 {
			nn += n
		}
		if nn == len(p) {
			break;
		}
		if err == os.EAGAIN && fd.wdeadline >= 0 {
			pollserver.WaitWrite(fd);
			continue;
		}
		if n == 0 || err != nil {
			break;
		}
	}
	return nn, err
}

func sockaddrToHostPort(sa *syscall.Sockaddr) (hostport string, err *os.Error)

func (fd *netFD) Accept(sa *syscall.Sockaddr) (nfd *netFD, err *os.Error) {
	if fd == nil || fd.file == nil {
		return nil, os.EINVAL
	}

	// See ../syscall/exec.go for description of ForkLock.
	// It is okay to hold the lock across syscall.Accept
	// because we have put fd.fd into non-blocking mode.
	syscall.ForkLock.RLock();
	var s, e int64;
	for {
		s, e = syscall.Accept(fd.fd, sa);
		if e != syscall.EAGAIN {
			break;
		}
		syscall.ForkLock.RUnlock();
		pollserver.WaitRead(fd);
		syscall.ForkLock.RLock();
	}
	if e != 0 {
		syscall.ForkLock.RUnlock();
		return nil, os.ErrnoToError(e)
	}
	syscall.CloseOnExec(s);
	syscall.ForkLock.RUnlock();

	raddr, err1 := sockaddrToHostPort(sa);
	if err1 != nil {
		raddr = "invalid-address";
	}
	if nfd, err = newFD(s, fd.net, fd.laddr, raddr); err != nil {
		syscall.Close(s);
		return nil, err
	}
	return nfd, nil
}

<commit_msg>document and partially fix a race<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// TODO(rsc): All the prints in this file should go to standard error.

package net

import (
	"net";
	"once";
	"os";
	"sync";
	"syscall";
)

// Network file descriptor.
type netFD struct {
	// immutable until Close
	fd int64;
	file *os.File;
	cr chan *netFD;
	cw chan *netFD;
	net string;
	laddr string;
	raddr string;

	// owned by client
	rdeadline_delta int64;
	rdeadline int64;
	rio sync.Mutex;
	wdeadline_delta int64;
	wdeadline int64;
	wio sync.Mutex;

	// owned by fd wait server
	ncr, ncw int;
}

// Make reads and writes on fd return EAGAIN instead of blocking.
func setNonblock(fd int64) *os.Error {
	flags, e := syscall.Fcntl(fd, syscall.F_GETFL, 0);
	if e != 0 {
		return os.ErrnoToError(e)
	}
	flags, e = syscall.Fcntl(fd, syscall.F_SETFL, flags | syscall.O_NONBLOCK);
	if e != 0 {
		return os.ErrnoToError(e)
	}
	return nil
}

// Make reads/writes blocking; last gasp, so no error checking.
func setBlock(fd int64) {
	flags, e := syscall.Fcntl(fd, syscall.F_GETFL, 0);
	if e != 0 {
		return;
	}
	syscall.Fcntl(fd, syscall.F_SETFL, flags & ^syscall.O_NONBLOCK);
}

// A pollServer helps FDs determine when to retry a non-blocking
// read or write after they get EAGAIN.  When an FD needs to wait,
// send the fd on s.cr (for a read) or s.cw (for a write) to pass the
// request to the poll server.  Then receive on fd.cr/fd.cw.
// When the pollServer finds that i/o on FD should be possible
// again, it will send fd on fd.cr/fd.cw to wake any waiting processes.
// This protocol is implemented as s.WaitRead() and s.WaitWrite().
//
// There is one subtlety: when sending on s.cr/s.cw, the
// poll server is probably in a system call, waiting for an fd
// to become ready.  It's not looking at the request channels.
// To resolve this, the poll server waits not just on the FDs it has
// been given but also its own pipe.  After sending on the
// buffered channel s.cr/s.cw, WaitRead/WaitWrite writes a
// byte to the pipe, causing the pollServer's poll system call to
// return.  In response to the pipe being readable, the pollServer
// re-polls its request channels.
//
// Note that the ordering is "send request" and then "wake up server".
// If the operations were reversed, there would be a race: the poll
// server might wake up and look at the request channel, see that it
// was empty, and go back to sleep, all before the requester managed
// to send the request.  Because the send must complete before the wakeup,
// the request channel must be buffered.  A buffer of size 1 is sufficient
// for any request load.  If many processes are trying to submit requests,
// one will succeed, the pollServer will read the request, and then the
// channel will be empty for the next process's request.  A larger buffer
// might help batch requests.

type pollServer struct {
	cr, cw chan *netFD;	// buffered >= 1
	pr, pw *os.File;
	pending map[int64] *netFD;
	poll *pollster;	// low-level OS hooks
	deadline int64;	// next deadline (nsec since 1970)
}
func (s *pollServer) Run();

func newPollServer() (s *pollServer, err *os.Error) {
	s = new(pollServer);
	s.cr = make(chan *netFD, 1);
	s.cw = make(chan *netFD, 1);
	if s.pr, s.pw, err = os.Pipe(); err != nil {
		return nil, err
	}
	if err = setNonblock(s.pr.Fd()); err != nil {
	Error:
		s.pr.Close();
		s.pw.Close();
		return nil, err
	}
	if err = setNonblock(s.pw.Fd()); err != nil {
		goto Error
	}
	if s.poll, err = newpollster(); err != nil {
		goto Error
	}
	if err = s.poll.AddFD(s.pr.Fd(), 'r', true); err != nil {
		s.poll.Close();
		goto Error
	}
	s.pending = make(map[int64] *netFD);
	go s.Run();
	return s, nil
}

func (s *pollServer) AddFD(fd *netFD, mode int) {
	// TODO(rsc): This check handles a race between
	// one goroutine reading and another one closing,
	// but it doesn't solve the race completely:
	// it still could happen that one goroutine closes
	// but we read fd.fd before it does, and then
	// another goroutine creates a new open file with
	// that fd, which we'd now be referring to.
	// The fix is probably to send the Close call
	// through the poll server too, except that
	// not all Reads and Writes go through the poll
	// server even now.
	intfd := fd.fd;
	if intfd < 0 {
		// fd closed underfoot
		if mode == 'r' {
			fd.cr <- fd
		} else {
			fd.cw <- fd
		}
		return
	}
	if err := s.poll.AddFD(intfd, mode, false); err != nil {
		panicln("pollServer AddFD ", intfd, ": ", err.String(), "\n");
		return
	}

	var t int64;
	key := intfd << 1;
	if mode == 'r' {
		fd.ncr++;
		t = fd.rdeadline;
	} else {
		fd.ncw++;
		key++;
		t = fd.wdeadline;
	}
	s.pending[key] = fd;
	if t > 0 && (s.deadline == 0 || t < s.deadline) {
		s.deadline = t;
	}
}

func (s *pollServer) LookupFD(fd int64, mode int) *netFD {
	key := fd << 1;
	if mode == 'w' {
		key++;
	}
	netfd, ok := s.pending[key];
	if !ok {
		return nil
	}
	s.pending[key] = nil, false;
	return netfd
}

func (s *pollServer) WakeFD(fd *netFD, mode int) {
	if mode == 'r' {
		for fd.ncr > 0 {
			fd.ncr--;
			fd.cr <- fd
		}
	} else {
		for fd.ncw > 0 {
			fd.ncw--;
			fd.cw <- fd
		}
	}
}

func (s *pollServer) Now() int64 {
	sec, nsec, err := os.Time();
	if err != nil {
		panic("net: os.Time: ", err.String());
	}
	nsec += sec * 1e9;
	return nsec;
}

func (s *pollServer) CheckDeadlines() {
	now := s.Now();
	// TODO(rsc): This will need to be handled more efficiently,
	// probably with a heap indexed by wakeup time.

	var next_deadline int64;
	for key, fd := range s.pending {
		var t int64;
		var mode int;
		if key&1 == 0 {
			mode = 'r';
		} else {
			mode = 'w';
		}
		if mode == 'r' {
			t = fd.rdeadline;
		} else {
			t = fd.wdeadline;
		}
		if t > 0 {
			if t <= now {
				s.pending[key] = nil, false;
				if mode == 'r' {
					s.poll.DelFD(fd.fd, mode);
					fd.rdeadline = -1;
				} else {
					s.poll.DelFD(fd.fd, mode);
					fd.wdeadline = -1;
				}
				s.WakeFD(fd, mode);
			} else if next_deadline == 0 || t < next_deadline {
				next_deadline = t;
			}
		}
	}
	s.deadline = next_deadline;
}

func (s *pollServer) Run() {
	var scratch [100]byte;
	for {
		var t = s.deadline;
		if t > 0 {
			t = t - s.Now();
			if t < 0 {
				s.CheckDeadlines();
				continue;
			}
		}
		fd, mode, err := s.poll.WaitFD(t);
		if err != nil {
			print("pollServer WaitFD: ", err.String(), "\n");
			return
		}
		if fd < 0 {
			// Timeout happened.
			s.CheckDeadlines();
			continue;
		}
		if fd == s.pr.Fd() {
			// Drain our wakeup pipe.
			for nn, e := s.pr.Read(scratch); nn > 0; {
				nn, e = s.pr.Read(scratch)
			}

			// Read from channels
			for fd, ok := <-s.cr; ok; fd, ok = <-s.cr {
				s.AddFD(fd, 'r')
			}
			for fd, ok := <-s.cw; ok; fd, ok = <-s.cw {
				s.AddFD(fd, 'w')
			}
		} else {
			netfd := s.LookupFD(fd, mode);
			if netfd == nil {
				print("pollServer: unexpected wakeup for fd=", netfd, " mode=", string(mode), "\n");
				continue
			}
			s.WakeFD(netfd, mode);
		}
	}
}

func (s *pollServer) Wakeup() {
	var b [1]byte;
	s.pw.Write(b)
}

func (s *pollServer) WaitRead(fd *netFD) {
	s.cr <- fd;
	s.Wakeup();
	<-fd.cr
}

func (s *pollServer) WaitWrite(fd *netFD) {
	s.cr <- fd;
	s.Wakeup();
	<-fd.cr
}

// Network FD methods.
// All the network FDs use a single pollServer.

var pollserver *pollServer

func _StartServer() {
	p, err := newPollServer();
	if err != nil {
		print("Start pollServer: ", err.String(), "\n")
	}
	pollserver = p
}

func newFD(fd int64, net, laddr, raddr string) (f *netFD, err *os.Error) {
	if pollserver == nil {
		once.Do(_StartServer);
	}
	if err = setNonblock(fd); err != nil {
		return nil, err
	}
	f = new(netFD);
	f.fd = fd;
	f.net = net;
	f.laddr = laddr;
	f.raddr = raddr;
	f.file = os.NewFile(fd, "net: " + net + " " + laddr + " " + raddr);
	f.cr = make(chan *netFD, 1);
	f.cw = make(chan *netFD, 1);
	return f, nil
}

func (fd *netFD) Close() *os.Error {
	if fd == nil || fd.file == nil {
		return os.EINVAL
	}

	// In case the user has set linger,
	// switch to blocking mode so the close blocks.
	// As long as this doesn't happen often,
	// we can handle the extra OS processes.
	// Otherwise we'll need to use the pollserver
	// for Close too.  Sigh.
	setBlock(fd.file.Fd());

	e := fd.file.Close();
	fd.file = nil;
	fd.fd = -1;
	return e
}

func (fd *netFD) Read(p []byte) (n int, err *os.Error) {
	if fd == nil || fd.file == nil {
		return -1, os.EINVAL
	}
	fd.rio.Lock();
	defer fd.rio.Unlock();
	if fd.rdeadline_delta > 0 {
		fd.rdeadline = pollserver.Now() + fd.rdeadline_delta;
	} else {
		fd.rdeadline = 0;
	}
	n, err = fd.file.Read(p);
	for err == os.EAGAIN && fd.rdeadline >= 0 {
		pollserver.WaitRead(fd);
		n, err = fd.file.Read(p)
	}
	return n, err
}

func (fd *netFD) Write(p []byte) (n int, err *os.Error) {
	if fd == nil || fd.file == nil {
		return -1, os.EINVAL
	}
	fd.wio.Lock();
	defer fd.wio.Unlock();
	if fd.wdeadline_delta > 0 {
		fd.wdeadline = pollserver.Now() + fd.wdeadline_delta;
	} else {
		fd.wdeadline = 0;
	}
	err = nil;
	nn := 0;
	for nn < len(p) {
		n, err = fd.file.Write(p[nn:len(p)]);
		if n > 0 {
			nn += n
		}
		if nn == len(p) {
			break;
		}
		if err == os.EAGAIN && fd.wdeadline >= 0 {
			pollserver.WaitWrite(fd);
			continue;
		}
		if n == 0 || err != nil {
			break;
		}
	}
	return nn, err
}

func sockaddrToHostPort(sa *syscall.Sockaddr) (hostport string, err *os.Error)

func (fd *netFD) Accept(sa *syscall.Sockaddr) (nfd *netFD, err *os.Error) {
	if fd == nil || fd.file == nil {
		return nil, os.EINVAL
	}

	// See ../syscall/exec.go for description of ForkLock.
	// It is okay to hold the lock across syscall.Accept
	// because we have put fd.fd into non-blocking mode.
	syscall.ForkLock.RLock();
	var s, e int64;
	for {
		s, e = syscall.Accept(fd.fd, sa);
		if e != syscall.EAGAIN {
			break;
		}
		syscall.ForkLock.RUnlock();
		pollserver.WaitRead(fd);
		syscall.ForkLock.RLock();
	}
	if e != 0 {
		syscall.ForkLock.RUnlock();
		return nil, os.ErrnoToError(e)
	}
	syscall.CloseOnExec(s);
	syscall.ForkLock.RUnlock();

	raddr, err1 := sockaddrToHostPort(sa);
	if err1 != nil {
		raddr = "invalid-address";
	}
	if nfd, err = newFD(s, fd.net, fd.laddr, raddr); err != nil {
		syscall.Close(s);
		return nil, err
	}
	return nfd, nil
}

<|endoftext|>


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

package net

import (
	"context"
	"internal/nettrace"
	"internal/singleflight"
)

// protocols contains minimal mappings between internet protocol
// names and numbers for platforms that don't have a complete list of
// protocol numbers.
//
// See http://www.iana.org/assignments/protocol-numbers
//
// On Unix, this map is augmented by readProtocols via lookupProtocol.
var protocols = map[string]int{
	"icmp":      1,
	"igmp":      2,
	"tcp":       6,
	"udp":       17,
	"ipv6-icmp": 58,
}

// services contains minimal mappings between services names and port
// numbers for platforms that don't have a complete list of port numbers
// (some Solaris distros, nacl, etc).
// On Unix, this map is augmented by readServices via goLookupPort.
var services = map[string]map[string]int{
	"udp": {
		"domain": 53,
	},
	"tcp": {
		"ftp":    21,
		"ftps":   990,
		"gopher": 70, // ʕ◔ϖ◔ʔ
		"http":   80,
		"https":  443,
		"imap2":  143,
		"imap3":  220,
		"imaps":  993,
		"pop3":   110,
		"pop3s":  995,
		"smtp":   25,
		"ssh":    22,
		"telnet": 23,
	},
}

const maxProtoLength = len("RSVP-E2E-IGNORE") + 10 // with room to grow

func lookupProtocolMap(name string) (int, error) {
	var lowerProtocol [maxProtoLength]byte
	n := copy(lowerProtocol[:], name)
	lowerASCIIBytes(lowerProtocol[:n])
	proto, found := protocols[string(lowerProtocol[:n])]
	if !found || n != len(name) {
		return 0, &AddrError{Err: "unknown IP protocol specified", Addr: name}
	}
	return proto, nil
}

const maxServiceLength = len("mobility-header") + 10 // with room to grow

func lookupPortMap(network, service string) (port int, error error) {
	switch network {
	case "tcp4", "tcp6":
		network = "tcp"
	case "udp4", "udp6":
		network = "udp"
	}

	if m, ok := services[network]; ok {
		var lowerService [maxServiceLength]byte
		n := copy(lowerService[:], service)
		lowerASCIIBytes(lowerService[:n])
		if port, ok := m[string(lowerService[:n])]; ok && n == len(service) {
			return port, nil
		}
	}
	return 0, &AddrError{Err: "unknown port", Addr: network + "/" + service}
}

// DefaultResolver is the resolver used by the package-level Lookup
// functions and by Dialers without a specified Resolver.
var DefaultResolver = &Resolver{}

// A Resolver looks up names and numbers.
//
// A nil *Resolver is equivalent to a zero Resolver.
type Resolver struct {
	// PreferGo controls whether Go's built-in DNS resolver is preferred
	// on platforms where it's available. It is equivalent to setting
	// GODEBUG=netdns=go, but scoped to just this resolver.
	PreferGo bool

	// StrictErrors controls the behavior of temporary errors
	// (including timeout, socket errors, and SERVFAIL) when using
	// Go's built-in resolver. For a query composed of multiple
	// sub-queries (such as an A+AAAA address lookup, or walking the
	// DNS search list), this option causes such errors to abort the
	// whole query instead of returning a partial result. This is
	// not enabled by default because it may affect compatibility
	// with resolvers that process AAAA queries incorrectly.
	StrictErrors bool

	// Dial optionally specifies an alternate dialer for use by
	// Go's built-in DNS resolver to make TCP and UDP connections
	// to DNS services. The host in the address parameter will
	// always be a literal IP address and not a host name, and the
	// port in the address parameter will be a literal port number
	// and not a service name.
	// If the Conn returned is also a PacketConn, sent and received DNS
	// messages must adhere to RFC 1035 section 4.2.1, "UDP usage".
	// Otherwise, DNS messages transmitted over Conn must adhere
	// to RFC 7766 section 5, "Transport Protocol Selection".
	// If nil, the default dialer is used.
	Dial func(ctx context.Context, network, address string) (Conn, error)

	// TODO(bradfitz): optional interface impl override hook
	// TODO(bradfitz): Timeout time.Duration?
}

// LookupHost looks up the given host using the local resolver.
// It returns a slice of that host's addresses.
func LookupHost(host string) (addrs []string, err error) {
	return DefaultResolver.LookupHost(context.Background(), host)
}

// LookupHost looks up the given host using the local resolver.
// It returns a slice of that host's addresses.
func (r *Resolver) LookupHost(ctx context.Context, host string) (addrs []string, err error) {
	// Make sure that no matter what we do later, host=="" is rejected.
	// ParseIP, for example, does accept empty strings.
	if host == "" {
		return nil, &DNSError{Err: errNoSuchHost.Error(), Name: host}
	}
	if ip := ParseIP(host); ip != nil {
		return []string{host}, nil
	}
	return r.lookupHost(ctx, host)
}

// LookupIP looks up host using the local resolver.
// It returns a slice of that host's IPv4 and IPv6 addresses.
func LookupIP(host string) ([]IP, error) {
	addrs, err := DefaultResolver.LookupIPAddr(context.Background(), host)
	if err != nil {
		return nil, err
	}
	ips := make([]IP, len(addrs))
	for i, ia := range addrs {
		ips[i] = ia.IP
	}
	return ips, nil
}

// LookupIPAddr looks up host using the local resolver.
// It returns a slice of that host's IPv4 and IPv6 addresses.
func (r *Resolver) LookupIPAddr(ctx context.Context, host string) ([]IPAddr, error) {
	// Make sure that no matter what we do later, host=="" is rejected.
	// ParseIP, for example, does accept empty strings.
	if host == "" {
		return nil, &DNSError{Err: errNoSuchHost.Error(), Name: host}
	}
	if ip := ParseIP(host); ip != nil {
		return []IPAddr{{IP: ip}}, nil
	}
	trace, _ := ctx.Value(nettrace.TraceKey{}).(*nettrace.Trace)
	if trace != nil && trace.DNSStart != nil {
		trace.DNSStart(host)
	}
	// The underlying resolver func is lookupIP by default but it
	// can be overridden by tests. This is needed by net/http, so it
	// uses a context key instead of unexported variables.
	resolverFunc := r.lookupIP
	if alt, _ := ctx.Value(nettrace.LookupIPAltResolverKey{}).(func(context.Context, string) ([]IPAddr, error)); alt != nil {
		resolverFunc = alt
	}

	ch := lookupGroup.DoChan(host, func() (interface{}, error) {
		return testHookLookupIP(ctx, resolverFunc, host)
	})

	select {
	case <-ctx.Done():
		// If the DNS lookup timed out for some reason, force
		// future requests to start the DNS lookup again
		// rather than waiting for the current lookup to
		// complete. See issue 8602.
		ctxErr := ctx.Err()
		if ctxErr == context.DeadlineExceeded {
			lookupGroup.Forget(host)
		}
		err := mapErr(ctxErr)
		if trace != nil && trace.DNSDone != nil {
			trace.DNSDone(nil, false, err)
		}
		return nil, err
	case r := <-ch:
		if trace != nil && trace.DNSDone != nil {
			addrs, _ := r.Val.([]IPAddr)
			trace.DNSDone(ipAddrsEface(addrs), r.Shared, r.Err)
		}
		return lookupIPReturn(r.Val, r.Err, r.Shared)
	}
}

// lookupGroup merges LookupIPAddr calls together for lookups
// for the same host. The lookupGroup key is is the LookupIPAddr.host
// argument.
// The return values are ([]IPAddr, error).
var lookupGroup singleflight.Group

// lookupIPReturn turns the return values from singleflight.Do into
// the return values from LookupIP.
func lookupIPReturn(addrsi interface{}, err error, shared bool) ([]IPAddr, error) {
	if err != nil {
		return nil, err
	}
	addrs := addrsi.([]IPAddr)
	if shared {
		clone := make([]IPAddr, len(addrs))
		copy(clone, addrs)
		addrs = clone
	}
	return addrs, nil
}

// ipAddrsEface returns an empty interface slice of addrs.
func ipAddrsEface(addrs []IPAddr) []interface{} {
	s := make([]interface{}, len(addrs))
	for i, v := range addrs {
		s[i] = v
	}
	return s
}

// LookupPort looks up the port for the given network and service.
func LookupPort(network, service string) (port int, err error) {
	return DefaultResolver.LookupPort(context.Background(), network, service)
}

// LookupPort looks up the port for the given network and service.
func (r *Resolver) LookupPort(ctx context.Context, network, service string) (port int, err error) {
	port, needsLookup := parsePort(service)
	if needsLookup {
		port, err = r.lookupPort(ctx, network, service)
		if err != nil {
			return 0, err
		}
	}
	if 0 > port || port > 65535 {
		return 0, &AddrError{Err: "invalid port", Addr: service}
	}
	return port, nil
}

// LookupCNAME returns the canonical name for the given host.
// Callers that do not care about the canonical name can call
// LookupHost or LookupIP directly; both take care of resolving
// the canonical name as part of the lookup.
//
// A canonical name is the final name after following zero
// or more CNAME records.
// LookupCNAME does not return an error if host does not
// contain DNS "CNAME" records, as long as host resolves to
// address records.
func LookupCNAME(host string) (cname string, err error) {
	return DefaultResolver.lookupCNAME(context.Background(), host)
}

// LookupCNAME returns the canonical name for the given host.
// Callers that do not care about the canonical name can call
// LookupHost or LookupIP directly; both take care of resolving
// the canonical name as part of the lookup.
//
// A canonical name is the final name after following zero
// or more CNAME records.
// LookupCNAME does not return an error if host does not
// contain DNS "CNAME" records, as long as host resolves to
// address records.
func (r *Resolver) LookupCNAME(ctx context.Context, host string) (cname string, err error) {
	return r.lookupCNAME(ctx, host)
}

// LookupSRV tries to resolve an SRV query of the given service,
// protocol, and domain name. The proto is "tcp" or "udp".
// The returned records are sorted by priority and randomized
// by weight within a priority.
//
// LookupSRV constructs the DNS name to look up following RFC 2782.
// That is, it looks up _service._proto.name. To accommodate services
// publishing SRV records under non-standard names, if both service
// and proto are empty strings, LookupSRV looks up name directly.
func LookupSRV(service, proto, name string) (cname string, addrs []*SRV, err error) {
	return DefaultResolver.lookupSRV(context.Background(), service, proto, name)
}

// LookupSRV tries to resolve an SRV query of the given service,
// protocol, and domain name. The proto is "tcp" or "udp".
// The returned records are sorted by priority and randomized
// by weight within a priority.
//
// LookupSRV constructs the DNS name to look up following RFC 2782.
// That is, it looks up _service._proto.name. To accommodate services
// publishing SRV records under non-standard names, if both service
// and proto are empty strings, LookupSRV looks up name directly.
func (r *Resolver) LookupSRV(ctx context.Context, service, proto, name string) (cname string, addrs []*SRV, err error) {
	return r.lookupSRV(ctx, service, proto, name)
}

// LookupMX returns the DNS MX records for the given domain name sorted by preference.
func LookupMX(name string) ([]*MX, error) {
	return DefaultResolver.lookupMX(context.Background(), name)
}

// LookupMX returns the DNS MX records for the given domain name sorted by preference.
func (r *Resolver) LookupMX(ctx context.Context, name string) ([]*MX, error) {
	return r.lookupMX(ctx, name)
}

// LookupNS returns the DNS NS records for the given domain name.
func LookupNS(name string) ([]*NS, error) {
	return DefaultResolver.lookupNS(context.Background(), name)
}

// LookupNS returns the DNS NS records for the given domain name.
func (r *Resolver) LookupNS(ctx context.Context, name string) ([]*NS, error) {
	return r.lookupNS(ctx, name)
}

// LookupTXT returns the DNS TXT records for the given domain name.
func LookupTXT(name string) ([]string, error) {
	return DefaultResolver.lookupTXT(context.Background(), name)
}

// LookupTXT returns the DNS TXT records for the given domain name.
func (r *Resolver) LookupTXT(ctx context.Context, name string) ([]string, error) {
	return r.lookupTXT(ctx, name)
}

// LookupAddr performs a reverse lookup for the given address, returning a list
// of names mapping to that address.
//
// When using the host C library resolver, at most one result will be
// returned. To bypass the host resolver, use a custom Resolver.
func LookupAddr(addr string) (names []string, err error) {
	return DefaultResolver.lookupAddr(context.Background(), addr)
}

// LookupAddr performs a reverse lookup for the given address, returning a list
// of names mapping to that address.
func (r *Resolver) LookupAddr(ctx context.Context, addr string) (names []string, err error) {
	return r.lookupAddr(ctx, addr)
}
<commit_msg>net: clarify the length limit for service name<commit_after>// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package net

import (
	"context"
	"internal/nettrace"
	"internal/singleflight"
)

// protocols contains minimal mappings between internet protocol
// names and numbers for platforms that don't have a complete list of
// protocol numbers.
//
// See http://www.iana.org/assignments/protocol-numbers
//
// On Unix, this map is augmented by readProtocols via lookupProtocol.
var protocols = map[string]int{
	"icmp":      1,
	"igmp":      2,
	"tcp":       6,
	"udp":       17,
	"ipv6-icmp": 58,
}

// services contains minimal mappings between services names and port
// numbers for platforms that don't have a complete list of port numbers
// (some Solaris distros, nacl, etc).
//
// See https://www.iana.org/assignments/service-names-port-numbers
//
// On Unix, this map is augmented by readServices via goLookupPort.
var services = map[string]map[string]int{
	"udp": {
		"domain": 53,
	},
	"tcp": {
		"ftp":    21,
		"ftps":   990,
		"gopher": 70, // ʕ◔ϖ◔ʔ
		"http":   80,
		"https":  443,
		"imap2":  143,
		"imap3":  220,
		"imaps":  993,
		"pop3":   110,
		"pop3s":  995,
		"smtp":   25,
		"ssh":    22,
		"telnet": 23,
	},
}

const maxProtoLength = len("RSVP-E2E-IGNORE") + 10 // with room to grow

func lookupProtocolMap(name string) (int, error) {
	var lowerProtocol [maxProtoLength]byte
	n := copy(lowerProtocol[:], name)
	lowerASCIIBytes(lowerProtocol[:n])
	proto, found := protocols[string(lowerProtocol[:n])]
	if !found || n != len(name) {
		return 0, &AddrError{Err: "unknown IP protocol specified", Addr: name}
	}
	return proto, nil
}

// maxPortBufSize is the longest reasonable name of a service
// (non-numeric port).
// Currently the longest known IANA-unregistered name is
// "mobility-header", so we use that length, plus some slop in case
// something longer is added in the future.
const maxPortBufSize = len("mobility-header") + 10

func lookupPortMap(network, service string) (port int, error error) {
	switch network {
	case "tcp4", "tcp6":
		network = "tcp"
	case "udp4", "udp6":
		network = "udp"
	}

	if m, ok := services[network]; ok {
		var lowerService [maxPortBufSize]byte
		n := copy(lowerService[:], service)
		lowerASCIIBytes(lowerService[:n])
		if port, ok := m[string(lowerService[:n])]; ok && n == len(service) {
			return port, nil
		}
	}
	return 0, &AddrError{Err: "unknown port", Addr: network + "/" + service}
}

// DefaultResolver is the resolver used by the package-level Lookup
// functions and by Dialers without a specified Resolver.
var DefaultResolver = &Resolver{}

// A Resolver looks up names and numbers.
//
// A nil *Resolver is equivalent to a zero Resolver.
type Resolver struct {
	// PreferGo controls whether Go's built-in DNS resolver is preferred
	// on platforms where it's available. It is equivalent to setting
	// GODEBUG=netdns=go, but scoped to just this resolver.
	PreferGo bool

	// StrictErrors controls the behavior of temporary errors
	// (including timeout, socket errors, and SERVFAIL) when using
	// Go's built-in resolver. For a query composed of multiple
	// sub-queries (such as an A+AAAA address lookup, or walking the
	// DNS search list), this option causes such errors to abort the
	// whole query instead of returning a partial result. This is
	// not enabled by default because it may affect compatibility
	// with resolvers that process AAAA queries incorrectly.
	StrictErrors bool

	// Dial optionally specifies an alternate dialer for use by
	// Go's built-in DNS resolver to make TCP and UDP connections
	// to DNS services. The host in the address parameter will
	// always be a literal IP address and not a host name, and the
	// port in the address parameter will be a literal port number
	// and not a service name.
	// If the Conn returned is also a PacketConn, sent and received DNS
	// messages must adhere to RFC 1035 section 4.2.1, "UDP usage".
	// Otherwise, DNS messages transmitted over Conn must adhere
	// to RFC 7766 section 5, "Transport Protocol Selection".
	// If nil, the default dialer is used.
	Dial func(ctx context.Context, network, address string) (Conn, error)

	// TODO(bradfitz): optional interface impl override hook
	// TODO(bradfitz): Timeout time.Duration?
}

// LookupHost looks up the given host using the local resolver.
// It returns a slice of that host's addresses.
func LookupHost(host string) (addrs []string, err error) {
	return DefaultResolver.LookupHost(context.Background(), host)
}

// LookupHost looks up the given host using the local resolver.
// It returns a slice of that host's addresses.
func (r *Resolver) LookupHost(ctx context.Context, host string) (addrs []string, err error) {
	// Make sure that no matter what we do later, host=="" is rejected.
	// ParseIP, for example, does accept empty strings.
	if host == "" {
		return nil, &DNSError{Err: errNoSuchHost.Error(), Name: host}
	}
	if ip := ParseIP(host); ip != nil {
		return []string{host}, nil
	}
	return r.lookupHost(ctx, host)
}

// LookupIP looks up host using the local resolver.
// It returns a slice of that host's IPv4 and IPv6 addresses.
func LookupIP(host string) ([]IP, error) {
	addrs, err := DefaultResolver.LookupIPAddr(context.Background(), host)
	if err != nil {
		return nil, err
	}
	ips := make([]IP, len(addrs))
	for i, ia := range addrs {
		ips[i] = ia.IP
	}
	return ips, nil
}

// LookupIPAddr looks up host using the local resolver.
// It returns a slice of that host's IPv4 and IPv6 addresses.
func (r *Resolver) LookupIPAddr(ctx context.Context, host string) ([]IPAddr, error) {
	// Make sure that no matter what we do later, host=="" is rejected.
	// ParseIP, for example, does accept empty strings.
	if host == "" {
		return nil, &DNSError{Err: errNoSuchHost.Error(), Name: host}
	}
	if ip := ParseIP(host); ip != nil {
		return []IPAddr{{IP: ip}}, nil
	}
	trace, _ := ctx.Value(nettrace.TraceKey{}).(*nettrace.Trace)
	if trace != nil && trace.DNSStart != nil {
		trace.DNSStart(host)
	}
	// The underlying resolver func is lookupIP by default but it
	// can be overridden by tests. This is needed by net/http, so it
	// uses a context key instead of unexported variables.
	resolverFunc := r.lookupIP
	if alt, _ := ctx.Value(nettrace.LookupIPAltResolverKey{}).(func(context.Context, string) ([]IPAddr, error)); alt != nil {
		resolverFunc = alt
	}

	ch := lookupGroup.DoChan(host, func() (interface{}, error) {
		return testHookLookupIP(ctx, resolverFunc, host)
	})

	select {
	case <-ctx.Done():
		// If the DNS lookup timed out for some reason, force
		// future requests to start the DNS lookup again
		// rather than waiting for the current lookup to
		// complete. See issue 8602.
		ctxErr := ctx.Err()
		if ctxErr == context.DeadlineExceeded {
			lookupGroup.Forget(host)
		}
		err := mapErr(ctxErr)
		if trace != nil && trace.DNSDone != nil {
			trace.DNSDone(nil, false, err)
		}
		return nil, err
	case r := <-ch:
		if trace != nil && trace.DNSDone != nil {
			addrs, _ := r.Val.([]IPAddr)
			trace.DNSDone(ipAddrsEface(addrs), r.Shared, r.Err)
		}
		return lookupIPReturn(r.Val, r.Err, r.Shared)
	}
}

// lookupGroup merges LookupIPAddr calls together for lookups
// for the same host. The lookupGroup key is is the LookupIPAddr.host
// argument.
// The return values are ([]IPAddr, error).
var lookupGroup singleflight.Group

// lookupIPReturn turns the return values from singleflight.Do into
// the return values from LookupIP.
func lookupIPReturn(addrsi interface{}, err error, shared bool) ([]IPAddr, error) {
	if err != nil {
		return nil, err
	}
	addrs := addrsi.([]IPAddr)
	if shared {
		clone := make([]IPAddr, len(addrs))
		copy(clone, addrs)
		addrs = clone
	}
	return addrs, nil
}

// ipAddrsEface returns an empty interface slice of addrs.
func ipAddrsEface(addrs []IPAddr) []interface{} {
	s := make([]interface{}, len(addrs))
	for i, v := range addrs {
		s[i] = v
	}
	return s
}

// LookupPort looks up the port for the given network and service.
func LookupPort(network, service string) (port int, err error) {
	return DefaultResolver.LookupPort(context.Background(), network, service)
}

// LookupPort looks up the port for the given network and service.
func (r *Resolver) LookupPort(ctx context.Context, network, service string) (port int, err error) {
	port, needsLookup := parsePort(service)
	if needsLookup {
		port, err = r.lookupPort(ctx, network, service)
		if err != nil {
			return 0, err
		}
	}
	if 0 > port || port > 65535 {
		return 0, &AddrError{Err: "invalid port", Addr: service}
	}
	return port, nil
}

// LookupCNAME returns the canonical name for the given host.
// Callers that do not care about the canonical name can call
// LookupHost or LookupIP directly; both take care of resolving
// the canonical name as part of the lookup.
//
// A canonical name is the final name after following zero
// or more CNAME records.
// LookupCNAME does not return an error if host does not
// contain DNS "CNAME" records, as long as host resolves to
// address records.
func LookupCNAME(host string) (cname string, err error) {
	return DefaultResolver.lookupCNAME(context.Background(), host)
}

// LookupCNAME returns the canonical name for the given host.
// Callers that do not care about the canonical name can call
// LookupHost or LookupIP directly; both take care of resolving
// the canonical name as part of the lookup.
//
// A canonical name is the final name after following zero
// or more CNAME records.
// LookupCNAME does not return an error if host does not
// contain DNS "CNAME" records, as long as host resolves to
// address records.
func (r *Resolver) LookupCNAME(ctx context.Context, host string) (cname string, err error) {
	return r.lookupCNAME(ctx, host)
}

// LookupSRV tries to resolve an SRV query of the given service,
// protocol, and domain name. The proto is "tcp" or "udp".
// The returned records are sorted by priority and randomized
// by weight within a priority.
//
// LookupSRV constructs the DNS name to look up following RFC 2782.
// That is, it looks up _service._proto.name. To accommodate services
// publishing SRV records under non-standard names, if both service
// and proto are empty strings, LookupSRV looks up name directly.
func LookupSRV(service, proto, name string) (cname string, addrs []*SRV, err error) {
	return DefaultResolver.lookupSRV(context.Background(), service, proto, name)
}

// LookupSRV tries to resolve an SRV query of the given service,
// protocol, and domain name. The proto is "tcp" or "udp".
// The returned records are sorted by priority and randomized
// by weight within a priority.
//
// LookupSRV constructs the DNS name to look up following RFC 2782.
// That is, it looks up _service._proto.name. To accommodate services
// publishing SRV records under non-standard names, if both service
// and proto are empty strings, LookupSRV looks up name directly.
func (r *Resolver) LookupSRV(ctx context.Context, service, proto, name string) (cname string, addrs []*SRV, err error) {
	return r.lookupSRV(ctx, service, proto, name)
}

// LookupMX returns the DNS MX records for the given domain name sorted by preference.
func LookupMX(name string) ([]*MX, error) {
	return DefaultResolver.lookupMX(context.Background(), name)
}

// LookupMX returns the DNS MX records for the given domain name sorted by preference.
func (r *Resolver) LookupMX(ctx context.Context, name string) ([]*MX, error) {
	return r.lookupMX(ctx, name)
}

// LookupNS returns the DNS NS records for the given domain name.
func LookupNS(name string) ([]*NS, error) {
	return DefaultResolver.lookupNS(context.Background(), name)
}

// LookupNS returns the DNS NS records for the given domain name.
func (r *Resolver) LookupNS(ctx context.Context, name string) ([]*NS, error) {
	return r.lookupNS(ctx, name)
}

// LookupTXT returns the DNS TXT records for the given domain name.
func LookupTXT(name string) ([]string, error) {
	return DefaultResolver.lookupTXT(context.Background(), name)
}

// LookupTXT returns the DNS TXT records for the given domain name.
func (r *Resolver) LookupTXT(ctx context.Context, name string) ([]string, error) {
	return r.lookupTXT(ctx, name)
}

// LookupAddr performs a reverse lookup for the given address, returning a list
// of names mapping to that address.
//
// When using the host C library resolver, at most one result will be
// returned. To bypass the host resolver, use a custom Resolver.
func LookupAddr(addr string) (names []string, err error) {
	return DefaultResolver.lookupAddr(context.Background(), addr)
}

// LookupAddr performs a reverse lookup for the given address, returning a list
// of names mapping to that address.
func (r *Resolver) LookupAddr(ctx context.Context, addr string) (names []string, err error) {
	return r.lookupAddr(ctx, addr)
}
<|endoftext|>


<commit_before>package record

// /CdsXSnhGV0TQ9B3VZ9IneNK4vk+K9k+

import (
	"encoding/base64"
	"encoding/json"
	"github.com/revel/revel"
	"io/ioutil"
	"replay/app/models/history"
	"strconv"
	"time"
)

var platformURLs map[string]string = map[string]string{
	"NA1":  "http://spectator.na.lol.riotgames.com:80",
	"OC1":  "http://spectator.oc1.lol.riotgames.com:80",
	"EUN1": "http://spectator.eu.lol.riotgames.com:8088",
	"EUW1": "http://spectator.euw1.lol.riotgames.com:80",
}

var version string // Version functions in getters.go
var recording map[string]map[string]string = make(map[string]map[string]string)

func writeRecording(region, gameId, key string, value []byte) {
	currentRecording := recording[region+":"+gameId]
	currentRecording[key] = base64.URLEncoding.EncodeToString(value)
	recording[region+":"+gameId] = currentRecording
}

func writeString(region, gameId, key string, value string) {
	currentRecording := recording[region+":"+gameId]
	currentRecording[key] = value
	recording[region+":"+gameId] = currentRecording
}

func existsRecording(region, gameId, key string) bool {
	if _, exists := recording[region+":"+gameId][key]; exists {
		return true
	}
	return false
}

func writeLastChunkInfo(region, gameId string,
	firstChunk, firstKeyFrame int, chunk ChunkInfo) {

	writeChunk := ChunkInfo{
		NextChunk:       firstChunk,
		CurrentChunk:    firstChunk,
		NextUpdate:      3000,
		StartGameChunk:  chunk.StartGameChunk,
		CurrentKeyFrame: firstKeyFrame,
		EndGameChunk:    chunk.CurrentChunk,
		AvailableSince:  0,
		Duration:        3000,
		EndStartupChunk: chunk.EndStartupChunk,
	}

	result, err := json.Marshal(writeChunk)
	if err != nil {
		panic("Error while encoding first chunk data json?!??!??")
	}

	writeRecording(region, gameId, "firstChunkData", result)

	writeChunk.NextChunk = chunk.CurrentChunk
	writeChunk.CurrentChunk = chunk.CurrentChunk
	writeChunk.CurrentKeyFrame = chunk.CurrentKeyFrame

	result, err = json.Marshal(writeChunk)
	if err != nil {
		panic("Error while encoding last chunk data json?!??!??")
	}

	writeRecording(region, gameId, "lastChunkData", result)
	writeString(region, gameId, "firstChunkNumber", strconv.Itoa(firstChunk))
}

func saveRecording(region, gameId string) {
	savePath := revel.BasePath + "/replays/" + region + "-" + gameId

	result, err := json.Marshal(recording[region+":"+gameId])
	if err != nil {
		panic("Error while encoding recording json?!?!?!?")
	}

	err = ioutil.WriteFile(savePath, result, 0644)
	if err != nil {
		revel.ERROR.Println("Error saving recording!")
	}
}

func recordMetadata(region, gameId string) {
	metadata := getMetadata(region, gameId)

	for {
		chunk := getLastChunkInfo(region, gameId)
		if chunk.CurrentChunk > metadata.StartupChunk {
			break
		}
		time.Sleep(time.Duration(chunk.NextUpdate)*time.Millisecond +
			time.Second)
	}

	metadata = getMetadata(region, gameId)

	// Get the startup frames
	for i := 1; i <= metadata.StartupChunk+1; i++ {
		// revel.INFO.Println("Getting startup chunk:", i)
		for {
			chunk := getLastChunkInfo(region, gameId)
			if i > chunk.CurrentChunk {
				time.Sleep(time.Duration(chunk.NextUpdate)*time.Millisecond +
					time.Second)
				continue
			}
			getChunkFrame(region, gameId, strconv.Itoa(i))
			break
		}
	}
}

func recordFrames(region, gameId string) {
	firstChunk := 0
	firstKeyFrame := 0
	lastChunk := 0
	lastKeyFrame := 0

	for {
		chunk := getLastChunkInfo(region, gameId)

		if firstChunk == 0 {
			if chunk.CurrentChunk > chunk.StartGameChunk {
				firstChunk = chunk.CurrentChunk
			} else {
				firstChunk = chunk.StartGameChunk
			}

			if chunk.CurrentKeyFrame > 0 {
				firstKeyFrame = chunk.CurrentKeyFrame
			} else {
				firstKeyFrame = 1
			}

			lastChunk = chunk.CurrentChunk
			lastKeyFrame = chunk.CurrentKeyFrame

			getChunkFrame(region, gameId, strconv.Itoa(chunk.CurrentChunk))
			getKeyFrame(region, gameId, strconv.Itoa(chunk.CurrentKeyFrame))
		}

		if chunk.StartGameChunk > firstChunk {
			firstChunk = chunk.StartGameChunk
		}

		if chunk.CurrentChunk > lastChunk {
			for i := lastChunk + 1; i <= chunk.CurrentChunk; i++ {
				getChunkFrame(region, gameId, strconv.Itoa(i))
			}
		}

		if chunk.NextChunk < chunk.CurrentChunk && chunk.NextChunk > 0 {
			getChunkFrame(region, gameId, strconv.Itoa(chunk.NextChunk))
		}

		if chunk.CurrentKeyFrame > lastKeyFrame {
			for i := lastKeyFrame + 1; i <= chunk.CurrentKeyFrame; i++ {
				getKeyFrame(region, gameId, strconv.Itoa(chunk.CurrentKeyFrame))
			}
		}

		writeLastChunkInfo(region, gameId, firstChunk, firstKeyFrame, chunk)
		saveRecording(region, gameId)

		lastChunk = chunk.CurrentChunk
		lastKeyFrame = chunk.CurrentKeyFrame

		if chunk.EndGameChunk == chunk.CurrentChunk {
			return
		}

		time.Sleep(time.Duration(chunk.NextUpdate)*time.Millisecond +
			time.Second)
	}
}

func asyncRecord(region, gameId, encryptionKey string) {
	defer func() {
		if r := recover(); r != nil {
			revel.ERROR.Println("Error while recording game ID: " + gameId)
			revel.ERROR.Println(r)
			delete(recording, region+":"+gameId)
		}
	}()

	writeRecording(region, gameId, "encryptionKey", []byte(encryptionKey))

	url := platformURLs[region]
	UpdateVersion(url)

	revel.INFO.Println("Now recording: " + region + ":" + gameId)
	revel.INFO.Println(gameId + "'s Encryption Key: " + encryptionKey)

	recordMetadata(region, gameId)
	recordFrames(region, gameId)

	revel.INFO.Println("Recording complete for: " + region + ":" + gameId)
	delete(recording, region+":"+gameId)
}

func Record(region, gameId, encryptionKey string) bool {
	if _, ok := recording[region+":"+gameId]; ok {
		return false
	} else {
		recording[region+":"+gameId] = make(map[string]string)
	}

	history.StoreGame(region, gameId, encryptionKey)

	go asyncRecord(region, gameId, encryptionKey)
	return true
}
<commit_msg>Prevent end of game overwrites<commit_after>package record

// /CdsXSnhGV0TQ9B3VZ9IneNK4vk+K9k+

import (
	"encoding/base64"
	"encoding/json"
	"github.com/revel/revel"
	"io/ioutil"
	"replay/app/models/history"
	"strconv"
	"time"
)

var platformURLs map[string]string = map[string]string{
	"NA1":  "http://spectator.na.lol.riotgames.com:80",
	"OC1":  "http://spectator.oc1.lol.riotgames.com:80",
	"EUN1": "http://spectator.eu.lol.riotgames.com:8088",
	"EUW1": "http://spectator.euw1.lol.riotgames.com:80",
}

var version string // Version functions in getters.go
var recording map[string]map[string]string = make(map[string]map[string]string)
var justRecorded []string = []string{"0", "0", "0", "0", "0"}

func writeRecording(region, gameId, key string, value []byte) {
	currentRecording := recording[region+":"+gameId]
	currentRecording[key] = base64.URLEncoding.EncodeToString(value)
	recording[region+":"+gameId] = currentRecording
}

func writeString(region, gameId, key string, value string) {
	currentRecording := recording[region+":"+gameId]
	currentRecording[key] = value
	recording[region+":"+gameId] = currentRecording
}

func existsRecording(region, gameId, key string) bool {
	if _, exists := recording[region+":"+gameId][key]; exists {
		return true
	}
	return false
}

func writeLastChunkInfo(region, gameId string,
	firstChunk, firstKeyFrame int, chunk ChunkInfo) {

	writeChunk := ChunkInfo{
		NextChunk:       firstChunk,
		CurrentChunk:    firstChunk,
		NextUpdate:      3000,
		StartGameChunk:  chunk.StartGameChunk,
		CurrentKeyFrame: firstKeyFrame,
		EndGameChunk:    chunk.CurrentChunk,
		AvailableSince:  0,
		Duration:        3000,
		EndStartupChunk: chunk.EndStartupChunk,
	}

	result, err := json.Marshal(writeChunk)
	if err != nil {
		panic("Error while encoding first chunk data json?!??!??")
	}

	writeRecording(region, gameId, "firstChunkData", result)

	writeChunk.NextChunk = chunk.CurrentChunk
	writeChunk.CurrentChunk = chunk.CurrentChunk
	writeChunk.CurrentKeyFrame = chunk.CurrentKeyFrame

	result, err = json.Marshal(writeChunk)
	if err != nil {
		panic("Error while encoding last chunk data json?!??!??")
	}

	writeRecording(region, gameId, "lastChunkData", result)
	writeString(region, gameId, "firstChunkNumber", strconv.Itoa(firstChunk))
}

func saveRecording(region, gameId string) {
	savePath := revel.BasePath + "/replays/" + region + "-" + gameId

	result, err := json.Marshal(recording[region+":"+gameId])
	if err != nil {
		panic("Error while encoding recording json?!?!?!?")
	}

	err = ioutil.WriteFile(savePath, result, 0644)
	if err != nil {
		revel.ERROR.Println("Error saving recording!")
	}
}

func recordMetadata(region, gameId string) {
	metadata := getMetadata(region, gameId)

	for {
		chunk := getLastChunkInfo(region, gameId)
		if chunk.CurrentChunk > metadata.StartupChunk {
			break
		}
		time.Sleep(time.Duration(chunk.NextUpdate)*time.Millisecond +
			time.Second)
	}

	metadata = getMetadata(region, gameId)

	// Get the startup frames
	for i := 1; i <= metadata.StartupChunk+1; i++ {
		// revel.INFO.Println("Getting startup chunk:", i)
		for {
			chunk := getLastChunkInfo(region, gameId)
			if i > chunk.CurrentChunk {
				time.Sleep(time.Duration(chunk.NextUpdate)*time.Millisecond +
					time.Second)
				continue
			}
			getChunkFrame(region, gameId, strconv.Itoa(i))
			break
		}
	}
}

func recordFrames(region, gameId string) {
	firstChunk := 0
	firstKeyFrame := 0
	lastChunk := 0
	lastKeyFrame := 0

	for {
		chunk := getLastChunkInfo(region, gameId)

		if firstChunk == 0 {
			if chunk.CurrentChunk > chunk.StartGameChunk {
				firstChunk = chunk.CurrentChunk
			} else {
				firstChunk = chunk.StartGameChunk
			}

			if chunk.CurrentKeyFrame > 0 {
				firstKeyFrame = chunk.CurrentKeyFrame
			} else {
				firstKeyFrame = 1
			}

			lastChunk = chunk.CurrentChunk
			lastKeyFrame = chunk.CurrentKeyFrame

			getChunkFrame(region, gameId, strconv.Itoa(chunk.CurrentChunk))
			getKeyFrame(region, gameId, strconv.Itoa(chunk.CurrentKeyFrame))
		}

		if chunk.StartGameChunk > firstChunk {
			firstChunk = chunk.StartGameChunk
		}

		if chunk.CurrentChunk > lastChunk {
			for i := lastChunk + 1; i <= chunk.CurrentChunk; i++ {
				getChunkFrame(region, gameId, strconv.Itoa(i))
			}
		}

		if chunk.NextChunk < chunk.CurrentChunk && chunk.NextChunk > 0 {
			getChunkFrame(region, gameId, strconv.Itoa(chunk.NextChunk))
		}

		if chunk.CurrentKeyFrame > lastKeyFrame {
			for i := lastKeyFrame + 1; i <= chunk.CurrentKeyFrame; i++ {
				getKeyFrame(region, gameId, strconv.Itoa(chunk.CurrentKeyFrame))
			}
		}

		writeLastChunkInfo(region, gameId, firstChunk, firstKeyFrame, chunk)
		saveRecording(region, gameId)

		lastChunk = chunk.CurrentChunk
		lastKeyFrame = chunk.CurrentKeyFrame

		if chunk.EndGameChunk == chunk.CurrentChunk {
			return
		}

		time.Sleep(time.Duration(chunk.NextUpdate)*time.Millisecond +
			time.Second)
	}
}

func appendJustRecorded(region, gameId) {
	justRecorded = append(justRecorded, region+":"+gameId)
	justRecorded = justRecorded[1:]
}

func asyncRecord(region, gameId, encryptionKey string) {
	defer func() {
		if r := recover(); r != nil {
			revel.ERROR.Println("Error while recording game ID: " + gameId)
			revel.ERROR.Println(r)
			delete(recording, region+":"+gameId)
		}
	}()

	writeRecording(region, gameId, "encryptionKey", []byte(encryptionKey))

	url := platformURLs[region]
	UpdateVersion(url)

	revel.INFO.Println("Now recording: " + region + ":" + gameId)
	revel.INFO.Println(gameId + "'s Encryption Key: " + encryptionKey)

	recordMetadata(region, gameId)
	recordFrames(region, gameId)

	revel.INFO.Println("Recording complete for: " + region + ":" + gameId)
	appendJustRecorded(region, gameId)
	delete(recording, region+":"+gameId)
}

func Record(region, gameId, encryptionKey string) bool {
	if _, ok := recording[region+":"+gameId]; ok {
		return false
	} else if _, ok := justRecorded[region+":"+gameId]; ok {
		return false
	} else {
		recording[region+":"+gameId] = make(map[string]string)
	}

	history.StoreGame(region, gameId, encryptionKey)

	go asyncRecord(region, gameId, encryptionKey)
	return true
}
<|endoftext|>