<commit_before>package spiffeid_test

import (
	"fmt"
	"net/url"
	"testing"

	"github.com/spiffe/go-spiffe/v2/spiffeid"
	"github.com/stretchr/testify/assert"
)

func TestTrustDomainFromString(t *testing.T) {
	tests := []struct {
		name          string
		input         string
		expectedTd    string
		expectedError string
	}{
		{
			name:       "has_uppercase_chars",
			input:      "DomAin.TesT",
			expectedTd: "domain.test",
		},
		{
			name:       "has_valid_scheme",
			input:      "spiffe://domain.test",
			expectedTd: "domain.test",
		},
		{
			name:       "is_valid_spiffe_id",
			input:      "spiffe://domain.test/path/element",
			expectedTd: "domain.test",
		},
		{
			name:       "is_valid_spiffe_id_with_spiffe_id_as_path",
			input:      "spiffe://domain.test/spiffe://domain.test/path/element",
			expectedTd: "domain.test",
		},
		{
			name:       "is_valid_spiffe_id_with_invalid_spiffe_id_as_path",
			input:      "spiffe://domain.test/spiffe://domain.test:80/path/element",
			expectedTd: "domain.test",
		},
		{
			name:          "has_invalid_scheme",
			input:         "http://domain.test",
			expectedError: "spiffeid: invalid scheme",
		},
		{
			name:          "missing_scheme",
			input:         "://domain.test",
			expectedError: "spiffeid: unable to parse: parse \"://domain.test\": missing protocol scheme",
		},
		{
			name:          "has_port",
			input:         "spiffe://domain.test:80",
			expectedError: "spiffeid: port is not allowed",
		},
		{
			name:          "has_colon",
			input:         "spiffe://domain.test:",
			expectedError: "spiffeid: colon is not allowed in trust domain",
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			td, err := spiffeid.TrustDomainFromString(test.input)
			if test.expectedError == "" {
				assert.NoError(t, err)
			} else {
				assert.EqualError(t, err, test.expectedError)
			}

			assert.Equal(t, test.expectedTd, td.String())
		})
	}
}

func TestRequireTrustDomainFromString(t *testing.T) {
	// Just test the panic, the non panicing cases are already handled by TestTrustDomainFromString.
	defer func() {
		if p := recover(); p != nil {
			assert.Equal(t, "spiffeid: colon is not allowed in trust domain", fmt.Sprintf("%v", p))
		}
	}()
	spiffeid.RequireTrustDomainFromString("spiffe://domain.test:")
}

func TestTrustDomainFromURI(t *testing.T) {
	tests := []struct {
		name          string
		input         *url.URL
		expectedTd    string
		expectedError string
	}{
		{
			name:       "happy_path",
			input:      parseURI(t, "spiffe://domain.test/path/element"),
			expectedTd: "domain.test",
		},
		{
			name:       "has_valid_scheme",
			input:      parseURI(t, "spiffe://domain.test"),
			expectedTd: "domain.test",
		},
		{
			name:       "is_valid_spiffe_id_with_spiffe_id_as_path",
			input:      parseURI(t, "spiffe://domain.test/spiffe://example.test/path/element"),
			expectedTd: "domain.test",
		},
		{
			name:       "is_valid_spiffe_id_with_invalid_spiffe_id_as_path",
			input:      parseURI(t, "spiffe://domain.test/spiffe://example.test:80/path/element"),
			expectedTd: "domain.test",
		},
		{
			name:       "has_uppercase_chars",
			input:      parseURI(t, "spiffe://DomAin.TesT"),
			expectedTd: "domain.test",
		},
		{
			name:          "has_invalid_scheme",
			input:         parseURI(t, "http://domain.test"),
			expectedError: "spiffeid: invalid scheme",
		},
		{
			name:          "missing_scheme",
			input:         &url.URL{Host: "domain.test"},
			expectedError: "spiffeid: invalid scheme",
		},
		{
			name:          "has_port",
			input:         parseURI(t, "spiffe://domain.test:80"),
			expectedError: "spiffeid: port is not allowed",
		},
		{
			name:          "has_colon",
			input:         parseURI(t, "spiffe://domain.test:"),
			expectedError: "spiffeid: colon is not allowed in trust domain",
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			td, err := spiffeid.TrustDomainFromURI(test.input)
			if test.expectedError == "" {
				assert.NoError(t, err)
			} else {
				assert.EqualError(t, err, test.expectedError)
			}

			assert.Equal(t, test.expectedTd, td.String())
		})
	}
}

func TestRequireTrustDomainFromURI(t *testing.T) {
	// Just test the panic, the non panicing cases are already handled by TestTrustDomainFromURI.
	defer func() {
		if p := recover(); p != nil {
			assert.Equal(t, "spiffeid: colon is not allowed in trust domain", fmt.Sprintf("%v", p))
		}
	}()
	spiffeid.RequireTrustDomainFromURI(parseURI(t, "spiffe://domain.test:"))
}

func TestTrustDomainID(t *testing.T) {
	id := spiffeid.Must("domain.test")

	// Common case
	td := spiffeid.RequireTrustDomainFromString("spiffe://domain.test/path/element")
	assert.Equal(t, id, td.ID())

	// Empty path
	td = spiffeid.RequireTrustDomainFromString("domain.test")
	assert.Equal(t, id, td.ID())
}

func TestTrustDomainIDString(t *testing.T) {
	// Common case
	td := spiffeid.RequireTrustDomainFromString("spiffe://domain.test/path/element")
	assert.Equal(t, "spiffe://domain.test", td.IDString())

	// Empty path
	td = spiffeid.RequireTrustDomainFromString("domain.test")
	assert.Equal(t, "spiffe://domain.test", td.IDString())

	// With uppercase letters
	td = spiffeid.RequireTrustDomainFromString("DoMain.TesT")
	assert.Equal(t, "spiffe://domain.test", td.IDString())
}

func TestTrustDomainNewID(t *testing.T) {
	td := spiffeid.RequireTrustDomainFromString("domain.test")

	// Common case
	id := spiffeid.Must("domain.test", "path", "element")
	assert.Equal(t, id, td.NewID("/path/element"))

	// Without starting with a slash
	id = spiffeid.Must("domain.test", "path", "element")
	assert.Equal(t, id, td.NewID("path/element"))

	// Empty path
	id = spiffeid.Must("domain.test")
	assert.Equal(t, id, td.NewID(""))
}

func TestTrustDomainEmpty(t *testing.T) {
	assert.True(t, spiffeid.TrustDomain{}.Empty())
}
<commit_msg>Addressed PR comments<commit_after>package spiffeid_test

import (
	"net/url"
	"testing"

	"github.com/spiffe/go-spiffe/v2/spiffeid"
	"github.com/stretchr/testify/assert"
)

func TestTrustDomainFromString(t *testing.T) {
	tests := []struct {
		name          string
		input         string
		expectedTd    string
		expectedError string
	}{
		{
			name:       "has_uppercase_chars",
			input:      "DomAin.TesT",
			expectedTd: "domain.test",
		},
		{
			name:       "has_valid_scheme",
			input:      "spiffe://domain.test",
			expectedTd: "domain.test",
		},
		{
			name:       "is_valid_spiffe_id",
			input:      "spiffe://domain.test/path/element",
			expectedTd: "domain.test",
		},
		{
			name:       "is_valid_spiffe_id_with_spiffe_id_as_path",
			input:      "spiffe://domain.test/spiffe://domain.test/path/element",
			expectedTd: "domain.test",
		},
		{
			name:       "is_valid_spiffe_id_with_invalid_spiffe_id_as_path",
			input:      "spiffe://domain.test/spiffe://domain.test:80/path/element",
			expectedTd: "domain.test",
		},
		{
			name:          "has_invalid_scheme",
			input:         "http://domain.test",
			expectedError: "spiffeid: invalid scheme",
		},
		{
			name:          "missing_scheme",
			input:         "://domain.test",
			expectedError: "spiffeid: unable to parse: parse \"://domain.test\": missing protocol scheme",
		},
		{
			name:          "has_port",
			input:         "spiffe://domain.test:80",
			expectedError: "spiffeid: port is not allowed",
		},
		{
			name:          "has_colon",
			input:         "spiffe://domain.test:",
			expectedError: "spiffeid: colon is not allowed in trust domain",
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			td, err := spiffeid.TrustDomainFromString(test.input)
			if test.expectedError == "" {
				assert.NoError(t, err)
			} else {
				assert.EqualError(t, err, test.expectedError)
			}

			assert.Equal(t, test.expectedTd, td.String())
		})
	}
}

func TestRequireTrustDomainFromString(t *testing.T) {
	// Just test the panic, the non panicing cases are already handled by TestTrustDomainFromString.
	assert.PanicsWithError(t, "spiffeid: colon is not allowed in trust domain", func() {
		spiffeid.RequireTrustDomainFromString("spiffe://domain.test:")
	})
}

func TestTrustDomainFromURI(t *testing.T) {
	tests := []struct {
		name          string
		input         *url.URL
		expectedTd    string
		expectedError string
	}{
		{
			name:       "happy_path",
			input:      parseURI(t, "spiffe://domain.test/path/element"),
			expectedTd: "domain.test",
		},
		{
			name:       "has_valid_scheme",
			input:      parseURI(t, "spiffe://domain.test"),
			expectedTd: "domain.test",
		},
		{
			name:       "is_valid_spiffe_id_with_spiffe_id_as_path",
			input:      parseURI(t, "spiffe://domain.test/spiffe://example.test/path/element"),
			expectedTd: "domain.test",
		},
		{
			name:       "is_valid_spiffe_id_with_invalid_spiffe_id_as_path",
			input:      parseURI(t, "spiffe://domain.test/spiffe://example.test:80/path/element"),
			expectedTd: "domain.test",
		},
		{
			name:       "has_uppercase_chars",
			input:      parseURI(t, "spiffe://DomAin.TesT"),
			expectedTd: "domain.test",
		},
		{
			name:          "has_invalid_scheme",
			input:         parseURI(t, "http://domain.test"),
			expectedError: "spiffeid: invalid scheme",
		},
		{
			name:          "missing_scheme",
			input:         &url.URL{Host: "domain.test"},
			expectedError: "spiffeid: invalid scheme",
		},
		{
			name:          "has_port",
			input:         parseURI(t, "spiffe://domain.test:80"),
			expectedError: "spiffeid: port is not allowed",
		},
		{
			name:          "has_colon",
			input:         parseURI(t, "spiffe://domain.test:"),
			expectedError: "spiffeid: colon is not allowed in trust domain",
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			td, err := spiffeid.TrustDomainFromURI(test.input)
			if test.expectedError == "" {
				assert.NoError(t, err)
			} else {
				assert.EqualError(t, err, test.expectedError)
			}

			assert.Equal(t, test.expectedTd, td.String())
		})
	}
}

func TestRequireTrustDomainFromURI(t *testing.T) {
	// Just test the panic, the non panicing cases are already handled by TestTrustDomainFromURI.
	assert.PanicsWithError(t, "spiffeid: colon is not allowed in trust domain", func() {
		spiffeid.RequireTrustDomainFromURI(parseURI(t, "spiffe://domain.test:"))
	})
}

func TestTrustDomainID(t *testing.T) {
	id := spiffeid.Must("domain.test")

	// Common case
	td := spiffeid.RequireTrustDomainFromString("spiffe://domain.test/path/element")
	assert.Equal(t, id, td.ID())

	// Empty path
	td = spiffeid.RequireTrustDomainFromString("domain.test")
	assert.Equal(t, id, td.ID())
}

func TestTrustDomainIDString(t *testing.T) {
	// Common case
	td := spiffeid.RequireTrustDomainFromString("spiffe://domain.test/path/element")
	assert.Equal(t, "spiffe://domain.test", td.IDString())

	// Empty path
	td = spiffeid.RequireTrustDomainFromString("domain.test")
	assert.Equal(t, "spiffe://domain.test", td.IDString())

	// With uppercase letters
	td = spiffeid.RequireTrustDomainFromString("DoMain.TesT")
	assert.Equal(t, "spiffe://domain.test", td.IDString())
}

func TestTrustDomainNewID(t *testing.T) {
	td := spiffeid.RequireTrustDomainFromString("domain.test")

	// Common case
	id := spiffeid.Must("domain.test", "path", "element")
	assert.Equal(t, id, td.NewID("/path/element"))

	// Without starting with a slash
	id = spiffeid.Must("domain.test", "path", "element")
	assert.Equal(t, id, td.NewID("path/element"))

	// Empty path
	id = spiffeid.Must("domain.test")
	assert.Equal(t, id, td.NewID(""))
}

func TestTrustDomainEmpty(t *testing.T) {
	assert.True(t, spiffeid.TrustDomain{}.Empty())
}
<|endoftext|>


<commit_before>// cmd_probes.go

package main

import (
	"fmt"
	"github.com/keltia/ripe-atlas"
	"github.com/urfave/cli"
	"log"
	"os"
	"strconv"
)

// init injects our probe-related commands
func init() {
	cliCommands = append(cliCommands, cli.Command{
		Name: "probes",
		Aliases: []string{
			"p",
			"pb",
		},
		Usage:       "probe-related keywords",
		Description: "All the commands for probes",
		Subcommands: []cli.Command{
			{
				Name:        "list",
				Aliases:     []string{"ls"},
				Usage:       "lists all probes",
				Description: "displays all probes",
				Flags: []cli.Flag{
					cli.StringFlag{
						Name:        "country, c",
						Usage:       "filter on country",
						Destination: &fCountry,
					},
					cli.StringFlag{
						Name:        "asn",
						Usage:       "filter on asn",
						Destination: &fAsn,
					},
					cli.BoolFlag{
						Name:        "A, all",
						Usage:       "all probes even inactive ones",
						Destination: &fAllProbes,
					},
					cli.BoolFlag{
						Name:        "is-anchor",
						Usage:       "select anchor probes",
						Destination: &fWantAnchor,
					},
				},
				Action: probesList,
			},
			{
				Name:        "info",
				Usage:       "info for one probe",
				Description: "gives info for one probe",
				Action:      probeInfo,
			},
		},
	})
}

// displayProbe display short or verbose data about a probe
func displayProbe(p *atlas.Probe, verbose bool) (res string) {
	if verbose {
		res = fmt.Sprintf("%v\n", p)
	} else {
		res = fmt.Sprintf("ID: %d Country: %s IPv4: %s IPv6: %s Descr: %s\n",
			p.ID,
			p.CountryCode,
			p.AddressV4,
			p.AddressV6,
			p.Description)
	}
	return
}

func displayAllProbes(pl *[]atlas.Probe, verbose bool) (res string) {
	res = ""
	for _, p := range *pl {
		// Do we want the inactive probes as well?
		if p.AddressV4 == "" && p.AddressV6 == "" {
			if !fAllProbes {
				continue
			}
		}
		res += displayProbe(&p, verbose)
	}
	return
}

// prepareTraceroute build the request with our parameters
func prepareProbes(country, asn string, anchor bool) (map[string]string) {
	opts := map[string]string{
		"Asn":         asn,
		"CountryCode": country,
		"IsAnchor":    fmt.Sprintf("%v", anchor),
	}

	if mycnf.WantAF != WantBoth {
		opts["AF"] = mycnf.WantAF
	}

	// Check global parameters
	opts = checkGlobalFlags(opts)

	if fVerbose {
		displayOptions(opts)
	}

	return opts
}

// probeList displays all probes
func probesList(c *cli.Context) error {
	opts := prepareProbes(fCountry, fAsn, fWantAnchor)
	q, err := atlas.GetProbes(opts)
	if err != nil {
		log.Printf("GetProbes err: %v - q:%v", err, q)
		os.Exit(1)
	}
	log.Printf("Got %d probes with %v\n", len(q), opts)
	fmt.Print(displayAllProbes(&q, fVerbose))

	return nil
}

// probeInfo is information about one probe
func probeInfo(c *cli.Context) error {
	args := c.Args()
	if len(args) == 0 {
		log.Fatal("Error: you must specify a probe ID!")
	}

	id, _ := strconv.Atoi(args[0])
	p, err := atlas.GetProbe(id)
	if err != nil {
		fmt.Printf("err: %v", err)
		os.Exit(1)
	}
	fmt.Print(displayProbe(p, fVerbose))

	return nil
}
<commit_msg>More Data displayed.<commit_after>// cmd_probes.go

package main

import (
	"fmt"
	"github.com/keltia/ripe-atlas"
	"github.com/urfave/cli"
	"log"
	"os"
	"strconv"
)

// init injects our probe-related commands
func init() {
	cliCommands = append(cliCommands, cli.Command{
		Name: "probes",
		Aliases: []string{
			"p",
			"pb",
		},
		Usage:       "probe-related keywords",
		Description: "All the commands for probes",
		Subcommands: []cli.Command{
			{
				Name:        "list",
				Aliases:     []string{"ls"},
				Usage:       "lists all probes",
				Description: "displays all probes",
				Flags: []cli.Flag{
					cli.StringFlag{
						Name:        "country, c",
						Usage:       "filter on country",
						Destination: &fCountry,
					},
					cli.StringFlag{
						Name:        "asn",
						Usage:       "filter on asn",
						Destination: &fAsn,
					},
					cli.BoolFlag{
						Name:        "A, all",
						Usage:       "all probes even inactive ones",
						Destination: &fAllProbes,
					},
					cli.BoolFlag{
						Name:        "is-anchor",
						Usage:       "select anchor probes",
						Destination: &fWantAnchor,
					},
				},
				Action: probesList,
			},
			{
				Name:        "info",
				Usage:       "info for one probe",
				Description: "gives info for one probe",
				Action:      probeInfo,
			},
		},
	})
}

// displayProbe display short or verbose data about a probe
func displayProbe(p *atlas.Probe, verbose bool) (res string) {
	if verbose {
		res = fmt.Sprintf("%v\n", p)
	} else {
		res = fmt.Sprintf("ID: %d Country: %s ASN4: %d IPv4: %s IPv6: %s Descr: %s\n",
			p.ID,
			p.CountryCode,
			p.AsnV4,
			p.AddressV4,
			p.AddressV6,
			p.Description)
	}
	return
}

func displayAllProbes(pl *[]atlas.Probe, verbose bool) (res string) {
	res = ""
	for _, p := range *pl {
		// Do we want the inactive probes as well?
		if p.AddressV4 == "" && p.AddressV6 == "" {
			if !fAllProbes {
				continue
			}
		}
		res += displayProbe(&p, verbose)
	}
	return
}

// prepareTraceroute build the request with our parameters
func prepareProbes(country, asn string, anchor bool) (map[string]string) {
	opts := map[string]string{
		"Asn":         asn,
		"CountryCode": country,
		"IsAnchor":    fmt.Sprintf("%v", anchor),
	}

	if mycnf.WantAF != WantBoth {
		opts["AF"] = mycnf.WantAF
	}

	// Check global parameters
	opts = checkGlobalFlags(opts)

	if fVerbose {
		displayOptions(opts)
	}

	return opts
}

// probeList displays all probes
func probesList(c *cli.Context) error {
	opts := prepareProbes(fCountry, fAsn, fWantAnchor)
	q, err := atlas.GetProbes(opts)
	if err != nil {
		log.Printf("GetProbes err: %v - q:%v", err, q)
		os.Exit(1)
	}
	log.Printf("Got %d probes with %v\n", len(q), opts)
	fmt.Print(displayAllProbes(&q, fVerbose))

	return nil
}

// probeInfo is information about one probe
func probeInfo(c *cli.Context) error {
	args := c.Args()
	if len(args) == 0 {
		log.Fatal("Error: you must specify a probe ID!")
	}

	id, _ := strconv.Atoi(args[0])
	p, err := atlas.GetProbe(id)
	if err != nil {
		fmt.Printf("err: %v", err)
		os.Exit(1)
	}
	fmt.Print(displayProbe(p, fVerbose))

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestValidateFormat(t *testing.T) {
	err := validateFormat("foo")
	assert.EqualValues(t, false, err, "should be false")

	err = validateFormat("xml")
	assert.EqualValues(t, true, err, "should be true")
}

func TestAnalyzeTarget(t *testing.T) {
	proto, site, path, port := analyzeTarget("https://atlas.ripe.net/v2/api/")
	assert.EqualValues(t, "https", proto, "should be fhttps")
	assert.EqualValues(t, "atlas.ripe.net", site, "should be atlas.ripe.net")
	assert.EqualValues(t, 0, port, "should be 443")
	assert.EqualValues(t, "/v2/api/", path, "Error: path=%s should be /v2/api/")
}

func TestAnalyzeTarget_1(t *testing.T) {
	proto, site, path, port := analyzeTarget("https://atlas.ripe.net:443/v2/api/")
	assert.EqualValues(t, "https", proto, "should be fhttps")
	assert.EqualValues(t, "atlas.ripe.net", site, "should be atlas.ripe.net")
	assert.EqualValues(t, 443, port, "should be 443")
	assert.EqualValues(t, "/v2/api/", path, "Error: path=%s should be /v2/api/")
}

func TestAnalyzeTarget_2(t *testing.T) {

	proto, site, path, port := analyzeTarget("http://b2b.cfmu:16443/api/")
	assert.EqualValues(t, "http", proto, "should be http")
	assert.EqualValues(t, "b2b.cfmu", site, "should be b2b.cfmu")
	assert.EqualValues(t, 16443, port, "should be 16443")
	assert.EqualValues(t, "/api/", path, "Error: path=%s should be /api/")
}

func TestAnalyzeTarget_3(t *testing.T) {
	proto, site, path, port := analyzeTarget("https://www.keltia.net")
	assert.EqualValues(t, "https", proto, "should be https")
	assert.EqualValues(t, "www.keltia.net", site, "should be keltia.net")
	assert.EqualValues(t, 0, port, "should be 443")
	assert.EqualValues(t, "/", path, "should be /")
}

var TestArgumentTypeData = []struct {
	Arg  string
	Type int
	AF   string
}{
	{"www.keltia.net", hostname, "64"},
	{"212.83.129.136", ipv4, "4"},
	{"2001:470:1f15:53d:1::2", ipv6, "6"},
}

func TestPrepareFamily(t *testing.T) {
	for _, d := range TestArgumentTypeData {
		prepareFamily(d.Arg)
		assert.Equal(t, d.AF, wantAF)
	}
}

func TestCheckArgumentType(t *testing.T) {
	for _, d := range TestArgumentTypeData {
		assert.Equal(t, d.Type, checkArgumentType(d.Arg))
	}
}
<commit_msg>Test for changed API for prepareFamily().<commit_after>package main

import (
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestValidateFormat(t *testing.T) {
	err := validateFormat("foo")
	assert.EqualValues(t, false, err, "should be false")

	err = validateFormat("xml")
	assert.EqualValues(t, true, err, "should be true")
}

func TestAnalyzeTarget(t *testing.T) {
	proto, site, path, port := analyzeTarget("https://atlas.ripe.net/v2/api/")
	assert.EqualValues(t, "https", proto, "should be fhttps")
	assert.EqualValues(t, "atlas.ripe.net", site, "should be atlas.ripe.net")
	assert.EqualValues(t, 0, port, "should be 443")
	assert.EqualValues(t, "/v2/api/", path, "Error: path=%s should be /v2/api/")
}

func TestAnalyzeTarget_1(t *testing.T) {
	proto, site, path, port := analyzeTarget("https://atlas.ripe.net:443/v2/api/")
	assert.EqualValues(t, "https", proto, "should be fhttps")
	assert.EqualValues(t, "atlas.ripe.net", site, "should be atlas.ripe.net")
	assert.EqualValues(t, 443, port, "should be 443")
	assert.EqualValues(t, "/v2/api/", path, "Error: path=%s should be /v2/api/")
}

func TestAnalyzeTarget_2(t *testing.T) {

	proto, site, path, port := analyzeTarget("http://b2b.cfmu:16443/api/")
	assert.EqualValues(t, "http", proto, "should be http")
	assert.EqualValues(t, "b2b.cfmu", site, "should be b2b.cfmu")
	assert.EqualValues(t, 16443, port, "should be 16443")
	assert.EqualValues(t, "/api/", path, "Error: path=%s should be /api/")
}

func TestAnalyzeTarget_3(t *testing.T) {
	proto, site, path, port := analyzeTarget("https://www.keltia.net")
	assert.EqualValues(t, "https", proto, "should be https")
	assert.EqualValues(t, "www.keltia.net", site, "should be keltia.net")
	assert.EqualValues(t, 0, port, "should be 443")
	assert.EqualValues(t, "/", path, "should be /")
}

var TestArgumentTypeData = []struct {
	Arg  string
	Type int
	AF   string
}{
	{"www.keltia.net", hostname, ""},
	{"212.83.129.136", ipv4, "4"},
	{"2001:470:1f15:53d:1::2", ipv6, "6"},
}

func TestPrepareFamily(t *testing.T) {
	for _, d := range TestArgumentTypeData {
		assert.Equal(t, d.AF, prepareFamily(d.Arg))
	}
}

func TestCheckArgumentType(t *testing.T) {
	for _, d := range TestArgumentTypeData {
		assert.Equal(t, d.Type, checkArgumentType(d.Arg))
	}
}
<|endoftext|>


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

package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"syscall"

	"github.com/BurntSushi/toml"
)

// Configuration stores all necessary
// information for checkmetrics execution
// and sent output as a report by email.
type Configuration struct {
	Mail MailConf
	Ck   Checkmetrics `toml:"checkmetrics"`
}

// MailConf stores the authentication
// configuration for sending the report
// by email. This information is parsed
// from TOML input file.
type MailConf struct {
	SMTP     string   // SMTP server address
	Port     string   // SMTP port
	User     string   // SMTP user
	Password string   // SMTP password
	ID       string   // SMTP indetity
	From     string   // Sender
	Subject  string   // Email msg subject
	To       []string // Receipts
	Cc       []string // Msg copy
}

// Checkmetrics stores the arguments
// for checkmetrics execution
type Checkmetrics struct {
	Cmd        string
	Basefile   string
	Metricsdir string
}

// System default path for configuration file
// the value will be set by Makefile
var sysConfFile string = ""

func main() {

	var confFile string
	var conf Configuration
	var body string
	var currentUID int
	var ownerUID int

	// Checkmetrics conf vars
	var cmd string
	var basefile string
	var metricsdir string

	flag.StringVar(&confFile, "f", "", "Configuration file")
	flag.Parse()

	// If there is not any input file specified by command line
	// it will look in default system path
	if confFile == "" {
		confFile = sysConfFile
	}

	// Get info about file
	fileinfo, err := os.Stat(confFile)
	if err != nil {
		log.Fatal(err)
	}

	// Owner verification
	currentUID = os.Geteuid()
	fStat := fileinfo.Sys().(*syscall.Stat_t)
	ownerUID = int(fStat.Uid)

	if currentUID != ownerUID {
		log.Fatal(currentUID,
			" is not the owner of ", confFile,
			" uid: ", ownerUID)
	}

	// Parsing TOML configuration file
	if _, err := toml.DecodeFile(confFile, &conf); err != nil {
		log.Fatal(err)
	}

	// Set checkmetrics configuration
	cmd = conf.Ck.Cmd
	basefile = "--basefile " + conf.Ck.Basefile
	metricsdir = "--metricsdir " + conf.Ck.Metricsdir

	// checkmetrics execution
	out, err := exec.Command(cmd, basefile, metricsdir).Output()
	if err != nil {
		log.Fatal(err)
	}

	if len(out) == 0 {
		log.Fatal("no output from: " + cmd)
	}

	body = fmt.Sprintf("%s", out)
	SendByEmail(conf, body)
}
<commit_msg>emailreport: Fix checkmetrics execution tool<commit_after>/// Copyight (c) 2017 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"syscall"

	"github.com/BurntSushi/toml"
)

// Configuration stores all necessary
// information for checkmetrics execution
// and sent output as a report by email.
type Configuration struct {
	Mail MailConf
	Ck   Checkmetrics `toml:"checkmetrics"`
}

// MailConf stores the authentication
// configuration for sending the report
// by email. This information is parsed
// from TOML input file.
type MailConf struct {
	SMTP     string   // SMTP server address
	Port     string   // SMTP port
	User     string   // SMTP user
	Password string   // SMTP password
	ID       string   // SMTP indetity
	From     string   // Sender
	Subject  string   // Email msg subject
	To       []string // Receipts
	Cc       []string // Msg copy
}

// Checkmetrics stores the arguments
// for checkmetrics execution
type Checkmetrics struct {
	Cmd        string
	Basefile   string
	Metricsdir string
}

// System default path for configuration file
// the value will be set by Makefile
var sysConfFile string = ""

func main() {

	var confFile string
	var conf Configuration
	var body string
	var currentUID int
	var ownerUID int

	// Checkmetrics conf vars
	var cmd string
	var basefile string
	var metricsdir string

	flag.StringVar(&confFile, "f", "", "Configuration file")
	flag.Parse()

	// If there is not any input file specified by command line
	// it will look in default system path
	if confFile == "" {
		confFile = sysConfFile
	}

	// Get info about file
	fileinfo, err := os.Stat(confFile)
	if err != nil {
		log.Fatal(err)
	}

	// Owner verification
	currentUID = os.Geteuid()
	fStat := fileinfo.Sys().(*syscall.Stat_t)
	ownerUID = int(fStat.Uid)

	if currentUID != ownerUID {
		log.Fatal(currentUID,
			" is not the owner of ", confFile,
			" uid: ", ownerUID)
	}

	// Parsing TOML configuration file
	if _, err := toml.DecodeFile(confFile, &conf); err != nil {
		log.Fatal(err)
	}

	// Set checkmetrics configuration
	cmd = conf.Ck.Cmd
	basefile = conf.Ck.Basefile
	metricsdir = conf.Ck.Metricsdir

	// checkmetrics execution
	out, err := exec.Command(cmd, "--basefile", basefile, "--metricsdir", metricsdir).Output()
	if err != nil {
		log.Fatal("checkmetrics execution: ", err)
	}

	if len(out) == 0 {
		log.Fatal("no output from: " + cmd)
	}

	body = fmt.Sprintf("%s", out)
	SendByEmail(conf, body)
}
<|endoftext|>


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

package cmd

import (
	"fmt"
	"hash"
	"strings"

	"github.com/minio/minio/pkg/errors"
)

// HealFile tries to reconstruct an erasure-coded file spread over all
// available disks. HealFile will read the valid parts of the file,
// reconstruct the missing data and write the reconstructed parts back
// to `staleDisks` at the destination `dstVol/dstPath/`. Parts are
// verified against the given BitrotAlgorithm and checksums.
//
// `staleDisks` is a slice of disks where each non-nil entry has stale
// or no data, and so will be healed.
//
// It is required that `s.disks` have a (read-quorum) majority of
// disks with valid data for healing to work.
//
// In addition, `staleDisks` and `s.disks` must have the same ordering
// of disks w.r.t. erasure coding of the object.
//
// Errors when writing to `staleDisks` are not propagated as long as
// writes succeed for at least one disk. This allows partial healing
// despite stale disks being faulty.
//
// It returns bitrot checksums for the non-nil staleDisks on which
// healing succeeded.
func (s ErasureStorage) HealFile(staleDisks []StorageAPI, volume, path string, blocksize int64,
	dstVol, dstPath string, size int64, alg BitrotAlgorithm, checksums [][]byte) (
	f ErasureFileInfo, err error) {

	if !alg.Available() {
		return f, errors.Trace(errBitrotHashAlgoInvalid)
	}

	// Initialization
	f.Checksums = make([][]byte, len(s.disks))
	hashers := make([]hash.Hash, len(s.disks))
	verifiers := make([]*BitrotVerifier, len(s.disks))
	for i, disk := range s.disks {
		switch {
		case staleDisks[i] != nil:
			hashers[i] = alg.New()
		case disk == nil:
			// disregard unavailable disk
			continue
		default:
			verifiers[i] = NewBitrotVerifier(alg, checksums[i])
		}
	}
	writeErrors := make([]error, len(s.disks))

	// Scan part files on disk, block-by-block reconstruct it and
	// write to stale disks.
	chunksize := getChunkSize(blocksize, s.dataBlocks)
	blocks := make([][]byte, len(s.disks))
	for i := range blocks {
		blocks[i] = make([]byte, chunksize)
	}
	var chunkOffset, blockOffset int64
	for ; blockOffset < size; blockOffset += blocksize {
		// last iteration may have less than blocksize data
		// left, so chunksize needs to be recomputed.
		if size < blockOffset+blocksize {
			blocksize = size - blockOffset
			chunksize = getChunkSize(blocksize, s.dataBlocks)
			for i := range blocks {
				blocks[i] = blocks[i][:chunksize]
			}
		}
		// read a chunk from each disk, until we have
		// `s.dataBlocks` number of chunks set to non-nil in
		// `blocks`
		numReads := 0
		for i, disk := range s.disks {
			// skip reading from unavailable or stale disks
			if disk == nil || staleDisks[i] != nil {
				blocks[i] = blocks[i][:0] // mark shard as missing
				continue
			}
			_, err = disk.ReadFile(volume, path, chunkOffset, blocks[i], verifiers[i])
			if err != nil {
				// LOG FIXME: add a conditional log
				// for read failures, once per-disk
				// per-function-invocation.
				blocks[i] = blocks[i][:0] // mark shard as missing
				continue
			}
			numReads++
			if numReads == s.dataBlocks {
				// we have enough data to reconstruct
				// mark all other blocks as missing
				for j := i + 1; j < len(blocks); j++ {
					blocks[j] = blocks[j][:0] // mark shard as missing
				}
				break
			}
		}

		// advance the chunk offset to prepare for next loop
		// iteration
		chunkOffset += chunksize

		// reconstruct data - this computes all data and parity shards
		if err = s.ErasureDecodeDataAndParityBlocks(blocks); err != nil {
			return f, err
		}

		// write computed shards as chunks on file in each
		// stale disk
		writeSucceeded := false
		for i, disk := range staleDisks {
			// skip nil disk or disk that had error on
			// previous write
			if disk == nil || writeErrors[i] != nil {
				continue
			}

			writeErrors[i] = disk.AppendFile(dstVol, dstPath, blocks[i])
			if writeErrors[i] == nil {
				hashers[i].Write(blocks[i])
				writeSucceeded = true
			}
		}

		// If all disks had write errors we quit.
		if !writeSucceeded {
			// build error from all write errors
			return f, errors.Trace(joinWriteErrors(writeErrors))
		}
	}

	// copy computed file hashes into output variable
	f.Size = size
	f.Algorithm = alg
	for i, disk := range staleDisks {
		if disk == nil || writeErrors[i] != nil {
			continue
		}
		f.Checksums[i] = hashers[i].Sum(nil)
	}
	return f, nil
}

func joinWriteErrors(errs []error) error {
	msgs := []string{}
	for i, err := range errs {
		if err == nil {
			continue
		}
		msgs = append(msgs, fmt.Sprintf("disk %d: %v", i+1, err))
	}
	return fmt.Errorf("all stale disks had write errors during healing: %s",
		strings.Join(msgs, ", "))
}
<commit_msg>fix: Restore empty files when healing (#5257)<commit_after>/*
 * Minio Cloud Storage, (C) 2016 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cmd

import (
	"fmt"
	"hash"
	"strings"

	"github.com/minio/minio/pkg/errors"
)

// HealFile tries to reconstruct an erasure-coded file spread over all
// available disks. HealFile will read the valid parts of the file,
// reconstruct the missing data and write the reconstructed parts back
// to `staleDisks` at the destination `dstVol/dstPath/`. Parts are
// verified against the given BitrotAlgorithm and checksums.
//
// `staleDisks` is a slice of disks where each non-nil entry has stale
// or no data, and so will be healed.
//
// It is required that `s.disks` have a (read-quorum) majority of
// disks with valid data for healing to work.
//
// In addition, `staleDisks` and `s.disks` must have the same ordering
// of disks w.r.t. erasure coding of the object.
//
// Errors when writing to `staleDisks` are not propagated as long as
// writes succeed for at least one disk. This allows partial healing
// despite stale disks being faulty.
//
// It returns bitrot checksums for the non-nil staleDisks on which
// healing succeeded.
func (s ErasureStorage) HealFile(staleDisks []StorageAPI, volume, path string, blocksize int64,
	dstVol, dstPath string, size int64, alg BitrotAlgorithm, checksums [][]byte) (
	f ErasureFileInfo, err error) {

	if !alg.Available() {
		return f, errors.Trace(errBitrotHashAlgoInvalid)
	}

	// Initialization
	f.Checksums = make([][]byte, len(s.disks))
	hashers := make([]hash.Hash, len(s.disks))
	verifiers := make([]*BitrotVerifier, len(s.disks))
	for i, disk := range s.disks {
		switch {
		case staleDisks[i] != nil:
			hashers[i] = alg.New()
		case disk == nil:
			// disregard unavailable disk
			continue
		default:
			verifiers[i] = NewBitrotVerifier(alg, checksums[i])
		}
	}
	writeErrors := make([]error, len(s.disks))

	// Scan part files on disk, block-by-block reconstruct it and
	// write to stale disks.
	chunksize := getChunkSize(blocksize, s.dataBlocks)
	blocks := make([][]byte, len(s.disks))
	for i := range blocks {
		blocks[i] = make([]byte, chunksize)
	}
	var chunkOffset, blockOffset int64

	// The for loop below is entered when size == 0 and
	// blockOffset == 0 to allow for reconstructing empty files.
	for ; blockOffset == 0 || blockOffset < size; blockOffset += blocksize {
		// last iteration may have less than blocksize data
		// left, so chunksize needs to be recomputed.
		if size < blockOffset+blocksize {
			chunksize = getChunkSize(size-blockOffset, s.dataBlocks)
			for i := range blocks {
				blocks[i] = blocks[i][:chunksize]
			}
		}
		// read a chunk from each disk, until we have
		// `s.dataBlocks` number of chunks set to non-nil in
		// `blocks`
		numReads := 0
		for i, disk := range s.disks {
			// skip reading from unavailable or stale disks
			if disk == nil || staleDisks[i] != nil {
				blocks[i] = blocks[i][:0] // mark shard as missing
				continue
			}
			_, err = disk.ReadFile(volume, path, chunkOffset, blocks[i], verifiers[i])
			if err != nil {
				// LOG FIXME: add a conditional log
				// for read failures, once per-disk
				// per-function-invocation.
				blocks[i] = blocks[i][:0] // mark shard as missing
				continue
			}
			numReads++
			if numReads == s.dataBlocks {
				// we have enough data to reconstruct
				// mark all other blocks as missing
				for j := i + 1; j < len(blocks); j++ {
					blocks[j] = blocks[j][:0] // mark shard as missing
				}
				break
			}
		}

		// advance the chunk offset to prepare for next loop
		// iteration
		chunkOffset += chunksize

		// reconstruct data - this computes all data and
		// parity shards - but we skip this step if we are
		// reconstructing an empty file.
		if chunksize > 0 {
			if err = s.ErasureDecodeDataAndParityBlocks(blocks); err != nil {
				return f, err
			}
		}

		// write computed shards as chunks on file in each
		// stale disk
		writeSucceeded := false
		for i, disk := range staleDisks {
			// skip nil disk or disk that had error on
			// previous write
			if disk == nil || writeErrors[i] != nil {
				continue
			}

			writeErrors[i] = disk.AppendFile(dstVol, dstPath, blocks[i])
			if writeErrors[i] == nil {
				hashers[i].Write(blocks[i])
				writeSucceeded = true
			}
		}

		// If all disks had write errors we quit.
		if !writeSucceeded {
			// build error from all write errors
			return f, errors.Trace(joinWriteErrors(writeErrors))
		}
	}

	// copy computed file hashes into output variable
	f.Size = size
	f.Algorithm = alg
	for i, disk := range staleDisks {
		if disk == nil || writeErrors[i] != nil {
			continue
		}
		f.Checksums[i] = hashers[i].Sum(nil)
	}
	return f, nil
}

func joinWriteErrors(errs []error) error {
	msgs := []string{}
	for i, err := range errs {
		if err == nil {
			continue
		}
		msgs = append(msgs, fmt.Sprintf("disk %d: %v", i+1, err))
	}
	return fmt.Errorf("all stale disks had write errors during healing: %s",
		strings.Join(msgs, ", "))
}
<|endoftext|>


<commit_before>package main

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

	"github.com/immortal/immortal"
)

var version string

const FORMAT = "%+7v %+15s %+15s   %-10s %-10s\n"

// immortal-ctl options service
// immortal-ctl status (print status of all services)
func main() {
	var (
		v    = flag.Bool("v", false, fmt.Sprintf("Print version: %s", version))
		sdir string
	)

	// if IMMORTAL_SDIR env is set, use it as default sdir
	if sdir = os.Getenv("IMMORTAL_SDIR"); sdir == "" {
		sdir = "/var/run/immortal"
	}

	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "usage: %s [option] [signal] service\n\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n\n",
			os.Args[0],
			"  Options:",
			"    kill      Terminate service",
			"    once      Do not restart if service stops",
			"    restart   Restart the service",
			"    start     Start the service",
			"    status    Print status of services",
			"    stop      Stop the service",
			"  Signals:",
			"    a         ALRM",
			"    c         CONT",
			"    d         TERM",
			"    h         HUP",
			"    i         INT",
			"    in        TTIN",
			"    ou        TTOU",
			"    s         STOP",
			"    q         QUIT",
			"    t         TERM",
			"    q         QUIT",
			"    1         USR1",
			"    2         USR2",
			"    w         WINCH",
		)
		flag.PrintDefaults()
	}

	flag.Parse()

	if *v {
		fmt.Printf("%s\n", version)
		os.Exit(0)
	}

	// get status for all services
	services, _ := immortal.FindServices(sdir)

	// get user $HOME/.immortal services
	if usr, err := user.Current(); err == nil {
		if userServices, err := immortal.FindServices(
			filepath.Join(usr.HomeDir, ".immortal"),
		); err == nil {
			services = append(services, userServices...)
		}
	}

	if len(services) > 0 {
		fmt.Printf(FORMAT, "PID", "Up", "Down", "Name", "CMD")
		for _, s := range services {
			status, err := immortal.GetStatus(s[1])
			if err != nil {
				immortal.PurgeServices(s[1])
			} else {
				if status.Down != "" {
					fmt.Printf(FORMAT, status.Pid, status.Up, status.Down, immortal.Red(fmt.Sprintf("%-10s", s[0])), status.Cmd)
				} else {
					fmt.Printf(FORMAT, status.Pid, status.Up, status.Down, immortal.Green(fmt.Sprintf("%-10s", s[0])), status.Cmd)
				}
			}
		}
	} else {
		println("No services found")
	}
}
<commit_msg>	modified:   cmd/immortalctl/main.go<commit_after>package main

import (
	"flag"
	"fmt"
	"os"
	"os/user"
	"path/filepath"
	"sync"

	"github.com/immortal/immortal"
)

var version string

const FORMAT = "%+7v %+15s %+15s   %-10s %-10s\n"

// immortal-ctl options service
// immortal-ctl status (print status of all services)
func main() {
	var (
		v    = flag.Bool("v", false, fmt.Sprintf("Print version: %s", version))
		sdir string
	)

	// if IMMORTAL_SDIR env is set, use it as default sdir
	if sdir = os.Getenv("IMMORTAL_SDIR"); sdir == "" {
		sdir = "/var/run/immortal"
	}

	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "usage: %s [option] [signal] service\n\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n\n",
			os.Args[0],
			"  Options:",
			"    kill      Terminate service",
			"    once      Do not restart if service stops",
			"    restart   Restart the service",
			"    start     Start the service",
			"    status    Print status of services",
			"    stop      Stop the service",
			"  Signals:",
			"    a         ALRM",
			"    c         CONT",
			"    d         TERM",
			"    h         HUP",
			"    i         INT",
			"    in        TTIN",
			"    ou        TTOU",
			"    s         STOP",
			"    q         QUIT",
			"    t         TERM",
			"    q         QUIT",
			"    1         USR1",
			"    2         USR2",
			"    w         WINCH",
		)
		flag.PrintDefaults()
	}

	flag.Parse()

	if *v {
		fmt.Printf("%s\n", version)
		os.Exit(0)
	}

	// get status for all services
	services, _ := immortal.FindServices(sdir)

	// get user $HOME/.immortal services
	if usr, err := user.Current(); err == nil {
		if userServices, err := immortal.FindServices(
			filepath.Join(usr.HomeDir, ".immortal"),
		); err == nil {
			services = append(services, userServices...)
		}
	}

	var wg sync.WaitGroup

	if len(services) > 0 {
		wg.Add(len(services))
		fmt.Printf(FORMAT, "PID", "Up", "Down", "Name", "CMD")
		for _, service := range services {
			go func(s []string) {
				defer wg.Done()
				status, err := immortal.GetStatus(s[1])
				if err != nil {
					immortal.PurgeServices(s[1])
				} else {
					if status.Down != "" {
						fmt.Printf(FORMAT, status.Pid, status.Up, status.Down, immortal.Red(fmt.Sprintf("%-10s", s[0])), status.Cmd)
					} else {
						fmt.Printf(FORMAT, status.Pid, status.Up, status.Down, immortal.Green(fmt.Sprintf("%-10s", s[0])), status.Cmd)
					}
				}
			}(service)
		}
	} else {
		println("No services found")
	}

	wg.Wait()
}
<|endoftext|>


<commit_before>// +build debug

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"runtime"
	"sort"
	"time"

	"github.com/spf13/cobra"
	"golang.org/x/sync/errgroup"

	"github.com/restic/restic/internal/crypto"
	"github.com/restic/restic/internal/errors"
	"github.com/restic/restic/internal/pack"
	"github.com/restic/restic/internal/repository"
	"github.com/restic/restic/internal/restic"
)

var cmdDebug = &cobra.Command{
	Use:   "debug",
	Short: "Debug commands",
}

var cmdDebugDump = &cobra.Command{
	Use:   "dump [indexes|snapshots|all|packs]",
	Short: "Dump data structures",
	Long: `
The "dump" command dumps data structures from the repository as JSON objects. It
is used for debugging purposes only.

EXIT STATUS
===========

Exit status is 0 if the command was successful, and non-zero if there was any error.
`,
	DisableAutoGenTag: true,
	RunE: func(cmd *cobra.Command, args []string) error {
		return runDebugDump(globalOptions, args)
	},
}

var tryRepair bool

func init() {
	cmdRoot.AddCommand(cmdDebug)
	cmdDebug.AddCommand(cmdDebugDump)
	cmdDebug.AddCommand(cmdDebugExamine)
	cmdDebugExamine.Flags().BoolVar(&tryRepair, "try-repair", false, "try to repair broken blobs with single bit flips")
}

func prettyPrintJSON(wr io.Writer, item interface{}) error {
	buf, err := json.MarshalIndent(item, "", "  ")
	if err != nil {
		return err
	}

	_, err = wr.Write(append(buf, '\n'))
	return err
}

func debugPrintSnapshots(ctx context.Context, repo *repository.Repository, wr io.Writer) error {
	return restic.ForAllSnapshots(ctx, repo, nil, func(id restic.ID, snapshot *restic.Snapshot, err error) error {
		if err != nil {
			return err
		}

		fmt.Fprintf(wr, "snapshot_id: %v\n", id)

		return prettyPrintJSON(wr, snapshot)
	})
}

// Pack is the struct used in printPacks.
type Pack struct {
	Name string `json:"name"`

	Blobs []Blob `json:"blobs"`
}

// Blob is the struct used in printPacks.
type Blob struct {
	Type   restic.BlobType `json:"type"`
	Length uint            `json:"length"`
	ID     restic.ID       `json:"id"`
	Offset uint            `json:"offset"`
}

func printPacks(ctx context.Context, repo *repository.Repository, wr io.Writer) error {

	return repo.List(ctx, restic.PackFile, func(id restic.ID, size int64) error {
		h := restic.Handle{Type: restic.PackFile, Name: id.String()}

		blobs, _, err := pack.List(repo.Key(), restic.ReaderAt(ctx, repo.Backend(), h), size)
		if err != nil {
			Warnf("error for pack %v: %v\n", id.Str(), err)
			return nil
		}

		p := Pack{
			Name:  id.String(),
			Blobs: make([]Blob, len(blobs)),
		}
		for i, blob := range blobs {
			p.Blobs[i] = Blob{
				Type:   blob.Type,
				Length: blob.Length,
				ID:     blob.ID,
				Offset: blob.Offset,
			}
		}

		return prettyPrintJSON(wr, p)
	})
}

func dumpIndexes(ctx context.Context, repo restic.Repository, wr io.Writer) error {
	return repository.ForAllIndexes(ctx, repo, func(id restic.ID, idx *repository.Index, oldFormat bool, err error) error {
		Printf("index_id: %v\n", id)
		if err != nil {
			return err
		}

		return idx.Dump(wr)
	})
}

func runDebugDump(gopts GlobalOptions, args []string) error {
	if len(args) != 1 {
		return errors.Fatal("type not specified")
	}

	repo, err := OpenRepository(gopts)
	if err != nil {
		return err
	}

	if !gopts.NoLock {
		lock, err := lockRepo(gopts.ctx, repo)
		defer unlockRepo(lock)
		if err != nil {
			return err
		}
	}

	tpe := args[0]

	switch tpe {
	case "indexes":
		return dumpIndexes(gopts.ctx, repo, gopts.stdout)
	case "snapshots":
		return debugPrintSnapshots(gopts.ctx, repo, gopts.stdout)
	case "packs":
		return printPacks(gopts.ctx, repo, gopts.stdout)
	case "all":
		Printf("snapshots:\n")
		err := debugPrintSnapshots(gopts.ctx, repo, gopts.stdout)
		if err != nil {
			return err
		}

		Printf("\nindexes:\n")
		err = dumpIndexes(gopts.ctx, repo, gopts.stdout)
		if err != nil {
			return err
		}

		return nil
	default:
		return errors.Fatalf("no such type %q", tpe)
	}
}

var cmdDebugExamine = &cobra.Command{
	Use:               "examine",
	Short:             "Examine a pack file",
	DisableAutoGenTag: true,
	RunE: func(cmd *cobra.Command, args []string) error {
		return runDebugExamine(globalOptions, args)
	},
}

func tryRepairWithBitflip(ctx context.Context, key *crypto.Key, input []byte) {
	fmt.Printf("        trying to repair blob with single bit flip\n")

	ch := make(chan int)
	var wg errgroup.Group
	done := make(chan struct{})

	fmt.Printf("         spinning up %d worker functions\n", runtime.NumCPU())
	for i := 0; i < runtime.NumCPU(); i++ {
		wg.Go(func() error {
			// make a local copy of the buffer
			buf := make([]byte, len(input))
			copy(buf, input)

			for {
				select {
				case <-done:
					return nil
				case i := <-ch:
					for j := 0; j < 7; j++ {
						// flip bit
						buf[i] ^= (1 << uint(j))

						nonce, plaintext := buf[:key.NonceSize()], buf[key.NonceSize():]
						plaintext, err := key.Open(plaintext[:0], nonce, plaintext, nil)
						if err == nil {
							fmt.Printf("\n")
							fmt.Printf("        blob could be repaired by flipping bit %v in byte %v\n", j, i)
							fmt.Printf("        hash is %v\n", restic.Hash(plaintext))
							close(done)
							return nil
						}

						// flip bit back
						buf[i] ^= (1 << uint(j))
					}
				}
			}
		})
	}

	start := time.Now()
	info := time.Now()
outer:
	for i := range input {
		select {
		case ch <- i:
		case <-done:
			fmt.Printf("     done after %v\n", time.Since(start))
			break outer
		}

		if time.Since(info) > time.Second {
			secs := time.Since(start).Seconds()
			gps := float64(i) / secs
			remaining := len(input) - i
			eta := time.Duration(float64(remaining)/gps) * time.Second

			fmt.Printf("\r%d byte of %d done (%.2f%%), %.2f guesses per second, ETA %v",
				i, len(input), float32(i)/float32(len(input)),
				gps, eta)
			info = time.Now()
		}
	}

	var found bool
	select {
	case <-done:
		found = true
	default:
		close(done)
	}

	wg.Wait()

	if !found {
		fmt.Printf("\n        blob could not be repaired by single bit flip\n")
	}
}

func loadBlobs(ctx context.Context, repo restic.Repository, pack string, list []restic.Blob) error {
	be := repo.Backend()
	for _, blob := range list {
		fmt.Printf("      loading blob %v at %v (length %v)\n", blob.ID, blob.Offset, blob.Length)
		buf := make([]byte, blob.Length)
		h := restic.Handle{
			Name: pack,
			Type: restic.PackFile,
		}
		err := be.Load(ctx, h, int(blob.Length), int64(blob.Offset), func(rd io.Reader) error {
			n, err := io.ReadFull(rd, buf)
			if err != nil {
				fmt.Fprintf(os.Stderr, "read error after %d bytes: %v\n", n, err)
				return err
			}
			return nil
		})
		if err != nil {
			fmt.Fprintf(os.Stderr, "error read: %v\n", err)
			continue
		}

		key := repo.Key()

		nonce, plaintext := buf[:key.NonceSize()], buf[key.NonceSize():]
		plaintext, err = key.Open(plaintext[:0], nonce, plaintext, nil)
		if err != nil {
			fmt.Fprintf(os.Stderr, "error decrypting blob: %v\n", err)
			if tryRepair {
				tryRepairWithBitflip(ctx, key, buf)
			}
			continue
		}

		id := restic.Hash(plaintext)
		if !id.Equal(blob.ID) {
			fmt.Printf("         successfully decrypted blob (length %v), hash is %v, ID does not match, wanted %v\n", len(plaintext), id, blob.ID)
		} else {
			fmt.Printf("         successfully decrypted blob (length %v), hash is %v, ID matches\n", len(plaintext), id)
		}
	}

	return nil
}

func runDebugExamine(gopts GlobalOptions, args []string) error {
	repo, err := OpenRepository(gopts)
	if err != nil {
		return err
	}

	if !gopts.NoLock {
		lock, err := lockRepo(gopts.ctx, repo)
		defer unlockRepo(lock)
		if err != nil {
			return err
		}
	}

	err = repo.LoadIndex(gopts.ctx)
	if err != nil {
		return err
	}

	blobsLoaded := false
	for _, name := range args {
		fmt.Printf("examine %v\n", name)
		id, err := restic.ParseID(name)
		if err != nil {
			fmt.Fprintf(os.Stderr, "error: %v\n", err)
		}

		h := restic.Handle{
			Type: restic.PackFile,
			Name: name,
		}
		fi, err := repo.Backend().Stat(gopts.ctx, h)
		if err != nil {
			fmt.Fprintf(os.Stderr, "error: %v\n", err)
		}

		fmt.Printf("  file size is %v\n", fi.Size)
		fmt.Printf("  ========================================\n")
		fmt.Printf("  looking for info in the indexes\n")

		// examine all data the indexes have for the pack file
		for _, idx := range repo.Index().(*repository.MasterIndex).All() {
			idxIDs, err := idx.IDs()
			if err != nil {
				idxIDs = restic.IDs{}
			}

			blobs := idx.ListPack(id)
			if len(blobs) == 0 {
				continue
			}

			fmt.Printf("    index %v:\n", idxIDs)

			// track current size and offset
			var size, offset uint64

			sort.Slice(blobs, func(i, j int) bool {
				return blobs[i].Offset < blobs[j].Offset
			})

			for _, pb := range blobs {
				fmt.Printf("      %v blob %v, offset %-6d, raw length %-6d\n", pb.Type, pb.ID, pb.Offset, pb.Length)
				if offset != uint64(pb.Offset) {
					fmt.Printf("      hole in file, want offset %v, got %v\n", offset, pb.Offset)
				}
				offset += uint64(pb.Length)
				size += uint64(pb.Length)
			}

			// compute header size, per blob: 1 byte type, 4 byte length, 32 byte id
			size += uint64(restic.CiphertextLength(len(blobs) * (1 + 4 + 32)))
			// length in uint32 little endian
			size += 4

			if uint64(fi.Size) != size {
				fmt.Printf("      file sizes do not match: computed %v from index, file size is %v\n", size, fi.Size)
			} else {
				fmt.Printf("      file sizes match\n")
			}

			// convert list of blobs to []restic.Blob
			var list []restic.Blob
			for _, b := range blobs {
				list = append(list, b.Blob)
			}

			err = loadBlobs(gopts.ctx, repo, name, list)
			if err != nil {
				fmt.Fprintf(os.Stderr, "error: %v\n", err)
			} else {
				blobsLoaded = true
			}
		}

		fmt.Printf("  ========================================\n")
		fmt.Printf("  inspect the pack itself\n")

		blobs, _, err := pack.List(repo.Key(), restic.ReaderAt(gopts.ctx, repo.Backend(), h), fi.Size)
		if err != nil {
			fmt.Fprintf(os.Stderr, "error for pack %v: %v\n", id.Str(), err)
			return nil
		}

		// track current size and offset
		var size, offset uint64

		sort.Slice(blobs, func(i, j int) bool {
			return blobs[i].Offset < blobs[j].Offset
		})

		for _, pb := range blobs {
			fmt.Printf("      %v blob %v, offset %-6d, raw length %-6d\n", pb.Type, pb.ID, pb.Offset, pb.Length)
			if offset != uint64(pb.Offset) {
				fmt.Printf("      hole in file, want offset %v, got %v\n", offset, pb.Offset)
			}
			offset += uint64(pb.Length)
			size += uint64(pb.Length)
		}

		// compute header size, per blob: 1 byte type, 4 byte length, 32 byte id
		size += uint64(restic.CiphertextLength(len(blobs) * (1 + 4 + 32)))
		// length in uint32 little endian
		size += 4

		if uint64(fi.Size) != size {
			fmt.Printf("      file sizes do not match: computed %v from index, file size is %v\n", size, fi.Size)
		} else {
			fmt.Printf("      file sizes match\n")
		}

		if !blobsLoaded {
			err = loadBlobs(gopts.ctx, repo, name, blobs)
			if err != nil {
				fmt.Fprintf(os.Stderr, "error: %v\n", err)
			}
		}
	}
	return nil
}
<commit_msg>debug: check file content hash<commit_after>// +build debug

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"runtime"
	"sort"
	"time"

	"github.com/spf13/cobra"
	"golang.org/x/sync/errgroup"

	"github.com/restic/restic/internal/backend"
	"github.com/restic/restic/internal/crypto"
	"github.com/restic/restic/internal/errors"
	"github.com/restic/restic/internal/pack"
	"github.com/restic/restic/internal/repository"
	"github.com/restic/restic/internal/restic"
)

var cmdDebug = &cobra.Command{
	Use:   "debug",
	Short: "Debug commands",
}

var cmdDebugDump = &cobra.Command{
	Use:   "dump [indexes|snapshots|all|packs]",
	Short: "Dump data structures",
	Long: `
The "dump" command dumps data structures from the repository as JSON objects. It
is used for debugging purposes only.

EXIT STATUS
===========

Exit status is 0 if the command was successful, and non-zero if there was any error.
`,
	DisableAutoGenTag: true,
	RunE: func(cmd *cobra.Command, args []string) error {
		return runDebugDump(globalOptions, args)
	},
}

var tryRepair bool

func init() {
	cmdRoot.AddCommand(cmdDebug)
	cmdDebug.AddCommand(cmdDebugDump)
	cmdDebug.AddCommand(cmdDebugExamine)
	cmdDebugExamine.Flags().BoolVar(&tryRepair, "try-repair", false, "try to repair broken blobs with single bit flips")
}

func prettyPrintJSON(wr io.Writer, item interface{}) error {
	buf, err := json.MarshalIndent(item, "", "  ")
	if err != nil {
		return err
	}

	_, err = wr.Write(append(buf, '\n'))
	return err
}

func debugPrintSnapshots(ctx context.Context, repo *repository.Repository, wr io.Writer) error {
	return restic.ForAllSnapshots(ctx, repo, nil, func(id restic.ID, snapshot *restic.Snapshot, err error) error {
		if err != nil {
			return err
		}

		fmt.Fprintf(wr, "snapshot_id: %v\n", id)

		return prettyPrintJSON(wr, snapshot)
	})
}

// Pack is the struct used in printPacks.
type Pack struct {
	Name string `json:"name"`

	Blobs []Blob `json:"blobs"`
}

// Blob is the struct used in printPacks.
type Blob struct {
	Type   restic.BlobType `json:"type"`
	Length uint            `json:"length"`
	ID     restic.ID       `json:"id"`
	Offset uint            `json:"offset"`
}

func printPacks(ctx context.Context, repo *repository.Repository, wr io.Writer) error {

	return repo.List(ctx, restic.PackFile, func(id restic.ID, size int64) error {
		h := restic.Handle{Type: restic.PackFile, Name: id.String()}

		blobs, _, err := pack.List(repo.Key(), restic.ReaderAt(ctx, repo.Backend(), h), size)
		if err != nil {
			Warnf("error for pack %v: %v\n", id.Str(), err)
			return nil
		}

		p := Pack{
			Name:  id.String(),
			Blobs: make([]Blob, len(blobs)),
		}
		for i, blob := range blobs {
			p.Blobs[i] = Blob{
				Type:   blob.Type,
				Length: blob.Length,
				ID:     blob.ID,
				Offset: blob.Offset,
			}
		}

		return prettyPrintJSON(wr, p)
	})
}

func dumpIndexes(ctx context.Context, repo restic.Repository, wr io.Writer) error {
	return repository.ForAllIndexes(ctx, repo, func(id restic.ID, idx *repository.Index, oldFormat bool, err error) error {
		Printf("index_id: %v\n", id)
		if err != nil {
			return err
		}

		return idx.Dump(wr)
	})
}

func runDebugDump(gopts GlobalOptions, args []string) error {
	if len(args) != 1 {
		return errors.Fatal("type not specified")
	}

	repo, err := OpenRepository(gopts)
	if err != nil {
		return err
	}

	if !gopts.NoLock {
		lock, err := lockRepo(gopts.ctx, repo)
		defer unlockRepo(lock)
		if err != nil {
			return err
		}
	}

	tpe := args[0]

	switch tpe {
	case "indexes":
		return dumpIndexes(gopts.ctx, repo, gopts.stdout)
	case "snapshots":
		return debugPrintSnapshots(gopts.ctx, repo, gopts.stdout)
	case "packs":
		return printPacks(gopts.ctx, repo, gopts.stdout)
	case "all":
		Printf("snapshots:\n")
		err := debugPrintSnapshots(gopts.ctx, repo, gopts.stdout)
		if err != nil {
			return err
		}

		Printf("\nindexes:\n")
		err = dumpIndexes(gopts.ctx, repo, gopts.stdout)
		if err != nil {
			return err
		}

		return nil
	default:
		return errors.Fatalf("no such type %q", tpe)
	}
}

var cmdDebugExamine = &cobra.Command{
	Use:               "examine",
	Short:             "Examine a pack file",
	DisableAutoGenTag: true,
	RunE: func(cmd *cobra.Command, args []string) error {
		return runDebugExamine(globalOptions, args)
	},
}

func tryRepairWithBitflip(ctx context.Context, key *crypto.Key, input []byte) {
	fmt.Printf("        trying to repair blob with single bit flip\n")

	ch := make(chan int)
	var wg errgroup.Group
	done := make(chan struct{})

	fmt.Printf("         spinning up %d worker functions\n", runtime.NumCPU())
	for i := 0; i < runtime.NumCPU(); i++ {
		wg.Go(func() error {
			// make a local copy of the buffer
			buf := make([]byte, len(input))
			copy(buf, input)

			for {
				select {
				case <-done:
					return nil
				case i := <-ch:
					for j := 0; j < 7; j++ {
						// flip bit
						buf[i] ^= (1 << uint(j))

						nonce, plaintext := buf[:key.NonceSize()], buf[key.NonceSize():]
						plaintext, err := key.Open(plaintext[:0], nonce, plaintext, nil)
						if err == nil {
							fmt.Printf("\n")
							fmt.Printf("        blob could be repaired by flipping bit %v in byte %v\n", j, i)
							fmt.Printf("        hash is %v\n", restic.Hash(plaintext))
							close(done)
							return nil
						}

						// flip bit back
						buf[i] ^= (1 << uint(j))
					}
				}
			}
		})
	}

	start := time.Now()
	info := time.Now()
outer:
	for i := range input {
		select {
		case ch <- i:
		case <-done:
			fmt.Printf("     done after %v\n", time.Since(start))
			break outer
		}

		if time.Since(info) > time.Second {
			secs := time.Since(start).Seconds()
			gps := float64(i) / secs
			remaining := len(input) - i
			eta := time.Duration(float64(remaining)/gps) * time.Second

			fmt.Printf("\r%d byte of %d done (%.2f%%), %.2f guesses per second, ETA %v",
				i, len(input), float32(i)/float32(len(input)),
				gps, eta)
			info = time.Now()
		}
	}

	var found bool
	select {
	case <-done:
		found = true
	default:
		close(done)
	}

	wg.Wait()

	if !found {
		fmt.Printf("\n        blob could not be repaired by single bit flip\n")
	}
}

func loadBlobs(ctx context.Context, repo restic.Repository, pack string, list []restic.Blob) error {
	be := repo.Backend()
	for _, blob := range list {
		fmt.Printf("      loading blob %v at %v (length %v)\n", blob.ID, blob.Offset, blob.Length)
		buf := make([]byte, blob.Length)
		h := restic.Handle{
			Name: pack,
			Type: restic.PackFile,
		}
		err := be.Load(ctx, h, int(blob.Length), int64(blob.Offset), func(rd io.Reader) error {
			n, err := io.ReadFull(rd, buf)
			if err != nil {
				fmt.Fprintf(os.Stderr, "read error after %d bytes: %v\n", n, err)
				return err
			}
			return nil
		})
		if err != nil {
			fmt.Fprintf(os.Stderr, "error read: %v\n", err)
			continue
		}

		key := repo.Key()

		nonce, plaintext := buf[:key.NonceSize()], buf[key.NonceSize():]
		plaintext, err = key.Open(plaintext[:0], nonce, plaintext, nil)
		if err != nil {
			fmt.Fprintf(os.Stderr, "error decrypting blob: %v\n", err)
			if tryRepair {
				tryRepairWithBitflip(ctx, key, buf)
			}
			continue
		}

		id := restic.Hash(plaintext)
		if !id.Equal(blob.ID) {
			fmt.Printf("         successfully decrypted blob (length %v), hash is %v, ID does not match, wanted %v\n", len(plaintext), id, blob.ID)
		} else {
			fmt.Printf("         successfully decrypted blob (length %v), hash is %v, ID matches\n", len(plaintext), id)
		}
	}

	return nil
}

func runDebugExamine(gopts GlobalOptions, args []string) error {
	repo, err := OpenRepository(gopts)
	if err != nil {
		return err
	}

	if !gopts.NoLock {
		lock, err := lockRepo(gopts.ctx, repo)
		defer unlockRepo(lock)
		if err != nil {
			return err
		}
	}

	err = repo.LoadIndex(gopts.ctx)
	if err != nil {
		return err
	}

	blobsLoaded := false
	for _, name := range args {
		fmt.Printf("examine %v\n", name)
		id, err := restic.ParseID(name)
		if err != nil {
			fmt.Fprintf(os.Stderr, "error: %v\n", err)
		}

		h := restic.Handle{
			Type: restic.PackFile,
			Name: name,
		}
		fi, err := repo.Backend().Stat(gopts.ctx, h)
		if err != nil {
			fmt.Fprintf(os.Stderr, "error: %v\n", err)
		}

		fmt.Printf("  file size is %v\n", fi.Size)

		buf, err := backend.LoadAll(gopts.ctx, nil, repo.Backend(), h)
		if err != nil {
			fmt.Fprintf(os.Stderr, "error: %v\n", err)
		}

		gotID := restic.Hash(buf)
		if !id.Equal(gotID) {
			fmt.Printf("  wanted hash %v, got %v\n", id, gotID)
		} else {
			fmt.Printf("  hash for file content matches\n")
		}

		fmt.Printf("  ========================================\n")
		fmt.Printf("  looking for info in the indexes\n")

		// examine all data the indexes have for the pack file
		for _, idx := range repo.Index().(*repository.MasterIndex).All() {
			idxIDs, err := idx.IDs()
			if err != nil {
				idxIDs = restic.IDs{}
			}

			blobs := idx.ListPack(id)
			if len(blobs) == 0 {
				continue
			}

			fmt.Printf("    index %v:\n", idxIDs)

			// track current size and offset
			var size, offset uint64

			sort.Slice(blobs, func(i, j int) bool {
				return blobs[i].Offset < blobs[j].Offset
			})

			for _, pb := range blobs {
				fmt.Printf("      %v blob %v, offset %-6d, raw length %-6d\n", pb.Type, pb.ID, pb.Offset, pb.Length)
				if offset != uint64(pb.Offset) {
					fmt.Printf("      hole in file, want offset %v, got %v\n", offset, pb.Offset)
				}
				offset += uint64(pb.Length)
				size += uint64(pb.Length)
			}

			// compute header size, per blob: 1 byte type, 4 byte length, 32 byte id
			size += uint64(restic.CiphertextLength(len(blobs) * (1 + 4 + 32)))
			// length in uint32 little endian
			size += 4

			if uint64(fi.Size) != size {
				fmt.Printf("      file sizes do not match: computed %v from index, file size is %v\n", size, fi.Size)
			} else {
				fmt.Printf("      file sizes match\n")
			}

			// convert list of blobs to []restic.Blob
			var list []restic.Blob
			for _, b := range blobs {
				list = append(list, b.Blob)
			}

			err = loadBlobs(gopts.ctx, repo, name, list)
			if err != nil {
				fmt.Fprintf(os.Stderr, "error: %v\n", err)
			} else {
				blobsLoaded = true
			}
		}

		fmt.Printf("  ========================================\n")
		fmt.Printf("  inspect the pack itself\n")

		blobs, _, err := pack.List(repo.Key(), restic.ReaderAt(gopts.ctx, repo.Backend(), h), fi.Size)
		if err != nil {
			fmt.Fprintf(os.Stderr, "error for pack %v: %v\n", id.Str(), err)
			return nil
		}

		// track current size and offset
		var size, offset uint64

		sort.Slice(blobs, func(i, j int) bool {
			return blobs[i].Offset < blobs[j].Offset
		})

		for _, pb := range blobs {
			fmt.Printf("      %v blob %v, offset %-6d, raw length %-6d\n", pb.Type, pb.ID, pb.Offset, pb.Length)
			if offset != uint64(pb.Offset) {
				fmt.Printf("      hole in file, want offset %v, got %v\n", offset, pb.Offset)
			}
			offset += uint64(pb.Length)
			size += uint64(pb.Length)
		}

		// compute header size, per blob: 1 byte type, 4 byte length, 32 byte id
		size += uint64(restic.CiphertextLength(len(blobs) * (1 + 4 + 32)))
		// length in uint32 little endian
		size += 4

		if uint64(fi.Size) != size {
			fmt.Printf("      file sizes do not match: computed %v from index, file size is %v\n", size, fi.Size)
		} else {
			fmt.Printf("      file sizes match\n")
		}

		if !blobsLoaded {
			err = loadBlobs(gopts.ctx, repo, name, blobs)
			if err != nil {
				fmt.Fprintf(os.Stderr, "error: %v\n", err)
			}
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"

	"github.com/solo-io/squash/pkg/lite/kube"
)

func main() {
	var cfg kube.SquashConfig
	flag.BoolVar(&cfg.NoClean, "no-clean", false, "don't clean temporar pod when existing")
	flag.BoolVar(&cfg.ChooseDebugger, "no-guess-debugger", false, "don't auto detect debugger to use")
	flag.BoolVar(&cfg.ChoosePod, "no-guess-pod", false, "don't auto detect pod to use")
	flag.IntVar(&cfg.TimeoutSeconds, "timeout", 60, "timeout in seconds to wait for debug pod to be ready")
	flag.Parse()

	err := kube.StartDebugContainer(cfg)
	if err != nil {
		fmt.Println(err)
	}
}
<commit_msg>more usage<commit_after>package main

import (
	"flag"
	"fmt"
	"os"

	"github.com/solo-io/squash/pkg/lite/kube"
)

const descriptionUsage = `Normally squash lite requires no arguments. just run it!
it works by creating additional debug pod and then attaching to it.
Kubernetes with CRI is needed. Due to a technical limitation, squash-lite doesn't support
scratch images at the moment (squash lite relys on the 'ls' command present in the image).
`

func main() {
	var cfg kube.SquashConfig
	flag.Usage = func() {
		fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s:\n", os.Args[0])
		fmt.Fprintf(flag.CommandLine.Output(), "%s\n", descriptionUsage)
		flag.PrintDefaults()
	}

	flag.BoolVar(&cfg.NoClean, "no-clean", false, "don't clean temporar pod when existing")
	flag.BoolVar(&cfg.ChooseDebugger, "no-guess-debugger", false, "don't auto detect debugger to use")
	flag.BoolVar(&cfg.ChoosePod, "no-guess-pod", false, "don't auto detect pod to use")
	flag.IntVar(&cfg.TimeoutSeconds, "timeout", 60, "timeout in seconds to wait for debug pod to be ready")
	flag.Parse()

	err := kube.StartDebugContainer(cfg)
	if err != nil {
		fmt.Println(err)
	}
}
<|endoftext|>


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

// getty Open a TTY and invoke a shell
// There are no special options and no login support
// Also getty exits after starting the shell so if one exits the shell, there
// is no more shell!
//
// Synopsis:
//     getty <port> <baud> [term]
package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strconv"
	"syscall"

	"github.com/u-root/u-root/pkg/upath"
	"golang.org/x/sys/unix"
)

var (
	verbose = flag.Bool("v", false, "verbose log")
	debug   = func(string, ...interface{}) {}
	cmdList []string
	envs    []string
)

func init() {
	r := upath.UrootPath
	cmdList = []string{
		r("/bin/defaultsh"),
		r("/bin/sh"),
	}
}

func openPort(port string, baud int) (*os.File, error) {
	var bauds = map[int]uint32{
		0:       0,
		50:      unix.B50,
		75:      unix.B75,
		110:     unix.B110,
		134:     unix.B134,
		150:     unix.B150,
		200:     unix.B200,
		300:     unix.B300,
		600:     unix.B600,
		1200:    unix.B1200,
		1800:    unix.B1800,
		2400:    unix.B2400,
		4800:    unix.B4800,
		9600:    unix.B9600,
		19200:   unix.B19200,
		38400:   unix.B38400,
		57600:   unix.B57600,
		115200:  unix.B115200,
		230400:  unix.B230400,
		460800:  unix.B460800,
		500000:  unix.B500000,
		576000:  unix.B576000,
		921600:  unix.B921600,
		1000000: unix.B1000000,
		1152000: unix.B1152000,
		1500000: unix.B1500000,
		2000000: unix.B2000000,
		2500000: unix.B2500000,
		3000000: unix.B3000000,
		3500000: unix.B3500000,
		4000000: unix.B4000000,
	}
	rate, ok := bauds[baud]
	if !ok {
		return nil, fmt.Errorf("Unrecognized baud rate")
	}

	f, err := os.OpenFile("/dev/"+port, unix.O_RDWR|unix.O_NOCTTY|unix.O_NONBLOCK, 0620)
	if err != nil {
		return nil, err
	}

	fd := int(f.Fd())
	t, err := unix.IoctlGetTermios(fd, unix.TCGETS)
	if err != nil {
		return nil, err
	}

	if baud != 0 {
		t.Cflag &^= unix.CBAUD
		t.Cflag |= rate
		t.Ispeed = rate
		t.Ospeed = rate
	}

	/* Clear all except baud, stop bit and parity settings */
	t.Cflag &= unix.CBAUD | unix.CSTOPB | unix.PARENB | unix.PARODD
	/* Set: 8 bits; ignore Carrier Detect; enable receive */
	t.Cflag |= unix.CS8 | unix.CLOCAL | unix.CREAD
	t.Iflag = unix.ICRNL
	t.Lflag = unix.ICANON | unix.ISIG | unix.ECHO | unix.ECHOE | unix.ECHOK | unix.ECHOKE | unix.ECHOCTL
	/* non-raw output; add CR to each NL */
	t.Oflag = unix.OPOST | unix.ONLCR
	/* reads will block only if < 1 char is available */
	t.Cc[unix.VMIN] = 1
	/* no timeout (reads block forever) */
	t.Cc[unix.VTIME] = 0
	t.Line = 0

	err = unix.IoctlSetTermios(fd, unix.TCSETS, t)
	if err != nil {
		return nil, err
	}

	return f, nil
}

func main() {
	flag.Parse()

	if *verbose {
		debug = log.Printf
	}

	port := flag.Arg(0)
	baud, err := strconv.Atoi(flag.Arg(1))
	if err != nil {
		baud = 0
	}
	term := flag.Arg(2)

	fd, err := openPort(port, baud)
	if err != nil {
		debug("Unable to open port %s: %v", port, err)
	}

	// Output the u-root banner
	log.New(fd, "", log.LstdFlags).Printf("Welcome to u-root!")
	fmt.Fprintln(fd, `                              _`)
	fmt.Fprintln(fd, `   _   _      _ __ ___   ___ | |_`)
	fmt.Fprintln(fd, `  | | | |____| '__/ _ \ / _ \| __|`)
	fmt.Fprintln(fd, `  | |_| |____| | | (_) | (_) | |_`)
	fmt.Fprintln(fd, `   \__,_|    |_|  \___/ \___/ \__|`)
	fmt.Fprintln(fd)

	if term != "" {
		err = os.Setenv("TERM", term)
		if err != nil {
			debug("Unable to set 'TERM=%s': %v", port, err)
		}
	}
	envs = os.Environ()
	debug("envs %v", envs)

	for _, v := range cmdList {
		debug("Let's try to run %v", v)
		if _, err := os.Stat(v); os.IsNotExist(err) {
			debug("it's not there")
			continue
		}

		// I *love* special cases. Evaluate just the top-most symlink.
		//
		// In source mode, this would be a symlink like
		// /buildbin/defaultsh -> /buildbin/elvish ->
		// /buildbin/installcommand.
		//
		// To actually get the command to build, argv[0] has to end
		// with /elvish, so we resolve one level of symlink.
		if path.Base(v) == "defaultsh" {
			s, err := os.Readlink(v)
			if err == nil {
				v = s
			}
			debug("readlink of %v returns %v", v, s)
			// and, well, it might be a relative link.
			// We must go deeper.
			d, b := filepath.Split(v)
			d = filepath.Base(d)
			v = filepath.Join("/", os.Getenv("UROOT_ROOT"), d, b)
			debug("is now %v", v)
		}

		cmd := exec.Command(v)
		cmd.Env = envs
		cmd.Stdin, cmd.Stdout, cmd.Stderr = fd, fd, fd
		cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true, Ctty: int(fd.Fd())}
		debug("Run %v", cmd)
		if err := cmd.Start(); err != nil {
			log.Printf("Error starting %v: %v", v, err)
			continue
		}
		if err := cmd.Process.Release(); err != nil {
			log.Printf("Error releasing %v:%v", v, err)
		}
		// stop after first valid command
		return
	}
	log.Printf("getty: No suitable executable found in %+v", cmdList)
}
<commit_msg>getty: Follow some of the changes to init<commit_after>// Copyright 2019 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// getty Open a TTY and invoke a shell
// There are no special options and no login support
// Also getty exits after starting the shell so if one exits the shell, there
// is no more shell!
//
// Synopsis:
//     getty <port> <baud> [term]
package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"strconv"
	"syscall"

	"github.com/u-root/u-root/pkg/upath"
	"golang.org/x/sys/unix"
)

var (
	verbose = flag.Bool("v", false, "verbose log")
	debug   = func(string, ...interface{}) {}
	cmdList []string
	envs    []string
)

func init() {
	r := upath.UrootPath
	cmdList = []string{
		r("/bin/defaultsh"),
		r("/bin/sh"),
	}
}

func openPort(port string, baud int) (*os.File, error) {
	var bauds = map[int]uint32{
		0:       0,
		50:      unix.B50,
		75:      unix.B75,
		110:     unix.B110,
		134:     unix.B134,
		150:     unix.B150,
		200:     unix.B200,
		300:     unix.B300,
		600:     unix.B600,
		1200:    unix.B1200,
		1800:    unix.B1800,
		2400:    unix.B2400,
		4800:    unix.B4800,
		9600:    unix.B9600,
		19200:   unix.B19200,
		38400:   unix.B38400,
		57600:   unix.B57600,
		115200:  unix.B115200,
		230400:  unix.B230400,
		460800:  unix.B460800,
		500000:  unix.B500000,
		576000:  unix.B576000,
		921600:  unix.B921600,
		1000000: unix.B1000000,
		1152000: unix.B1152000,
		1500000: unix.B1500000,
		2000000: unix.B2000000,
		2500000: unix.B2500000,
		3000000: unix.B3000000,
		3500000: unix.B3500000,
		4000000: unix.B4000000,
	}
	rate, ok := bauds[baud]
	if !ok {
		return nil, fmt.Errorf("Unrecognized baud rate")
	}

	f, err := os.OpenFile("/dev/"+port, unix.O_RDWR|unix.O_NOCTTY|unix.O_NONBLOCK, 0620)
	if err != nil {
		return nil, err
	}

	fd := int(f.Fd())
	t, err := unix.IoctlGetTermios(fd, unix.TCGETS)
	if err != nil {
		return nil, err
	}

	if baud != 0 {
		t.Cflag &^= unix.CBAUD
		t.Cflag |= rate
		t.Ispeed = rate
		t.Ospeed = rate
	}

	/* Clear all except baud, stop bit and parity settings */
	t.Cflag &= unix.CBAUD | unix.CSTOPB | unix.PARENB | unix.PARODD
	/* Set: 8 bits; ignore Carrier Detect; enable receive */
	t.Cflag |= unix.CS8 | unix.CLOCAL | unix.CREAD
	t.Iflag = unix.ICRNL
	t.Lflag = unix.ICANON | unix.ISIG | unix.ECHO | unix.ECHOE | unix.ECHOK | unix.ECHOKE | unix.ECHOCTL
	/* non-raw output; add CR to each NL */
	t.Oflag = unix.OPOST | unix.ONLCR
	/* reads will block only if < 1 char is available */
	t.Cc[unix.VMIN] = 1
	/* no timeout (reads block forever) */
	t.Cc[unix.VTIME] = 0
	t.Line = 0

	err = unix.IoctlSetTermios(fd, unix.TCSETS, t)
	if err != nil {
		return nil, err
	}

	return f, nil
}

func main() {
	flag.Parse()

	if *verbose {
		debug = log.Printf
	}

	port := flag.Arg(0)
	baud, err := strconv.Atoi(flag.Arg(1))
	if err != nil {
		baud = 0
	}
	term := flag.Arg(2)

	fd, err := openPort(port, baud)
	if err != nil {
		debug("Unable to open port %s: %v", port, err)
	}

	// Output the u-root banner
	log.New(fd, "", log.LstdFlags).Printf("Welcome to u-root!")
	fmt.Fprintln(fd, `                              _`)
	fmt.Fprintln(fd, `   _   _      _ __ ___   ___ | |_`)
	fmt.Fprintln(fd, `  | | | |____| '__/ _ \ / _ \| __|`)
	fmt.Fprintln(fd, `  | |_| |____| | | (_) | (_) | |_`)
	fmt.Fprintln(fd, `   \__,_|    |_|  \___/ \___/ \__|`)
	fmt.Fprintln(fd)

	log.SetPrefix("getty: ")

	if term != "" {
		err = os.Setenv("TERM", term)
		if err != nil {
			debug("Unable to set 'TERM=%s': %v", port, err)
		}
	}
	envs = os.Environ()
	debug("envs %v", envs)

	for _, v := range cmdList {
		debug("Trying to run %v", v)
		if _, err := os.Stat(v); os.IsNotExist(err) {
			debug("%v", err)
			continue
		}

		cmd := exec.Command(v)
		cmd.Env = envs
		cmd.Stdin, cmd.Stdout, cmd.Stderr = fd, fd, fd
		cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true, Ctty: int(fd.Fd())}
		debug("running %v", cmd)
		if err := cmd.Start(); err != nil {
			log.Printf("Error starting %v: %v", v, err)
			continue
		}
		if err := cmd.Process.Release(); err != nil {
			log.Printf("Error releasing process %v:%v", v, err)
		}
		// stop after first valid command
		return
	}
	log.Printf("No suitable executable found in %+v", cmdList)
}
<|endoftext|>


<commit_before>// Copyright (c) 2014, Nick Patavalis (npat@efault.net).
// All rights reserved.
// Use of this source code is governed by a BSD-style license that can
// be found in the LICENSE.txt file.

/*

Create nPairs pairs of goroutines, and nPairs * 2 fifos. In every
goroutine pair, one goroutine is the Echoer and the other is the
Sender. They are connected by fifos like this:

  [ echoer ] <-fdr[2*i]------ fifo 2*i --------fdw[2*i]-- [ sender ]
  [   #i   ] --fdw[2*i+1]---- fifo 2*i+1 ----fdr[2*i+1]-> [   #i   ]

The Senders send messages (nMsg each) to the respective Echoers. The
Echoers echo the received messages back to the senders. The senders
check if the echoed-back messages are identical to the originals.

*/

package poller_test

import (
	"bytes"
	"fmt"
	"io"
	"math/rand"
	"runtime"
	"syscall"
	"testing"
	"time"

	"github.com/npat-efault/poller"
)

const (
	nPairs = 8   // Number of Sender / Echoer pairs
	nMsg   = 100 // Number of messages sent to each Echoer
)

// !! ATTENTION !! We unconditionally delete files named like this !!
const fifo = "/tmp/poller-test-fifo" // /tmp/poller-test-fifoXXX

func packUInt32BE(p []byte, i uint32) []byte {
	return append(p,
		byte(i>>24),
		byte(i>>16),
		byte(i>>8),
		byte(i))
}

func packUInt16BE(p []byte, i uint16) []byte {
	return append(p,
		byte(i>>8),
		byte(i))
}

func unpackUInt32BE(p []byte) (uint32, []byte) {
	i := uint32(p[0])<<24 |
		uint32(p[1])<<16 |
		uint32(p[2])<<8 |
		uint32(p[3])
	return i, p[4:]
}

func unpackUInt16BE(p []byte) (uint16, []byte) {
	i := uint16(p[0])<<8 |
		uint16(p[1])
	return i, p[2:]
}

func packRandData(p []byte, n int) []byte {
	// This is *very* slow due to the repetitive locking /
	// unlocking of the common random source.
	for i := 0; i < n; i++ {
		p = append(p, byte(rand.Intn(0x100)))
	}
	return p
}

// Message (msg):
//
// +--+--+--+--+--+--+--+-- .... ... +--+
// | len |    seq    |       data       |
// +--+--+--+--+--+--+--+-- .... ... +--+
// |                 |                  |
// |<--headSz (6)--->|<--len - headSz-->|

const (
	lenSz  = 2
	seqSz  = 4
	headSz = lenSz + seqSz
)

type msg []byte

func randMsg(seq uint32) msg {
	n := rand.Intn(0x10000 - headSz)
	l := headSz + n
	m := make(msg, 0, l)
	m = packUInt16BE(m, uint16(l))
	m = packUInt32BE(m, seq)
	m = packRandData(m, n)
	return m
}

func (m msg) Len() int {
	n, _ := unpackUInt16BE(m)
	return int(n)
}

func (m msg) Seq() uint32 {
	sq, _ := unpackUInt32BE(m[lenSz:])
	return sq
}

func (m msg) Data() []byte {
	return m[headSz:]
}

func (m msg) Send(w io.Writer) error {
	_, err := w.Write(m)
	return err
}

func rcvMsg(r io.Reader) (msg, error) {
	m0 := make(msg, lenSz)
	_, err := io.ReadFull(r, m0)
	if err != nil {
		return nil, err
	}
	l := m0.Len()
	m := make(msg, l)
	copy(m, m0)
	_, err = io.ReadFull(r, m[lenSz:])
	if err != nil {
		return nil, err
	}
	return m, nil
}

func sendN(r, w *poller.FD, n int) error {
	var err error
	for i := 0; i < n; i++ {
		var ms, mr msg
		ms = randMsg(uint32(i))
		err = w.SetWriteDeadline(time.Now().Add(2 * time.Second))
		if err != nil {
			err = fmt.Errorf("set W dl #%d: %v", i, err)
			break
		}
		err = ms.Send(w)
		if err != nil {
			err = fmt.Errorf("cannot send #%d: %v", i, err)
			break
		}
		err = r.SetReadDeadline(time.Now().Add(2 * time.Second))
		if err != nil {
			err = fmt.Errorf("set R dl #%d: %v", i, err)
			break
		}
		mr, err = rcvMsg(r)
		if err != nil {
			err = fmt.Errorf("cannot rcv #%d: %v", i, err)
			break
		}
		if mr.Seq() != uint32(i) {
			err = fmt.Errorf("rcv seq %d != %d", mr.Seq, i)
			break
		}
		if !bytes.Equal(ms.Data(), mr.Data()) {
			err = fmt.Errorf("data not equal for #%d", i)
			break
		}
	}
	return err
}

func echoN(r, w *poller.FD, n int) error {
	var err error
	for i := 0; i < n; i++ {
		var m msg
		err = r.SetReadDeadline(time.Now().Add(2 * time.Second))
		if err != nil {
			err = fmt.Errorf("set R dl #%d: %v", i, err)
			break
		}
		m, err = rcvMsg(r)
		if err != nil {
			err = fmt.Errorf("cannot rcv #%d: %v", i, err)
			break
		}
		if m.Seq() != uint32(i) {
			err = fmt.Errorf("rcv seq %d != %d", m.Seq, i)
			break
		}
		err = w.SetWriteDeadline(time.Now().Add(2 * time.Second))
		if err != nil {
			err = fmt.Errorf("set W dl #%d: %v", i, err)
			break
		}
		err = m.Send(w)
		if err != nil {
			err = fmt.Errorf("cannot send #%d: %v", i, err)
			break
		}
	}
	return err
}

func fifoName(i int) string {
	return fifo + fmt.Sprintf("%03d", i)
}

func mkFifo(t *testing.T, i int) {
	name := fifoName(i)
	syscall.Unlink(name)
	err := syscall.Mkfifo(name, 0666)
	if err != nil {
		t.Fatalf("mkFifo %s: %v", name, err)
	}
}

func rmFifo(t *testing.T, i int) {
	name := fifoName(i)
	err := syscall.Unlink(name)
	if err != nil {
		t.Fatalf("rmFifo %s: %v", name, err)
	}
}

func openFifo(t *testing.T, i int, read bool) *poller.FD {
	name := fifoName(i)
	flags := poller.O_RO
	if !read {
		flags = poller.O_WO
	}
	fd, err := poller.Open(name, flags)
	if err != nil {
		t.Fatalf("Open %s: %v", name, err)
	}
	return fd
}

func waitNTmo(t *testing.T, ch <-chan error, n int, d time.Duration) {
	tmo := time.After(d)
	for i := 0; i < n; i++ {
		select {
		case err := <-ch:
			if err != nil {
				_, _, line, _ := runtime.Caller(1)
				t.Fatalf("%d: %v", line, err)
			}
		case <-tmo:
			_, _, line, _ := runtime.Caller(1)
			t.Fatalf("%d: Timeout!", line)
		}
	}
}

func TestEcho(t *testing.T) {
	fdr := make([]*poller.FD, nPairs*2)
	fdw := make([]*poller.FD, nPairs*2)
	for i := 0; i < nPairs; i++ {
		// even fifos: echoers read, senders write
		// odd fifos: echoes write, senders read
		mkFifo(t, 2*i)
		mkFifo(t, 2*i+1)
		fdr[2*i] = openFifo(t, 2*i, true)
		fdw[2*i] = openFifo(t, 2*i, false)
		fdr[2*i+1] = openFifo(t, 2*i+1, true)
		fdw[2*i+1] = openFifo(t, 2*i+1, false)
	}

	ch := make(chan error)

	for i := 0; i < nPairs; i++ {
		go func(i int) {
			err := echoN(fdr[2*i], fdw[2*i+1], nMsg)
			if err != nil {
				err = fmt.Errorf("Echoer %d: %v", i, err)
			}
			ch <- err
		}(i)
		go func(i int) {
			err := sendN(fdr[2*i+1], fdw[2*i], nMsg)
			if err != nil {
				err = fmt.Errorf("Sender %d: %v", i, err)
			}
			ch <- err
		}(i)
	}
	waitNTmo(t, ch, nPairs*2, 60*time.Second)

	for i := 0; i < nPairs; i++ {
		if err := fdr[2*i].Close(); err != nil {
			t.Fatalf("Failed to close fdr[%d]: %v", 2*i, err)
		}
		if err := fdw[2*i].Close(); err != nil {
			t.Fatalf("Failed to close fdw[%d]: %v", 2*i, err)
		}
		if err := fdr[2*i+1].Close(); err != nil {
			t.Fatalf("Failed to close fdr[%d]: %v", 2*i+1, err)
		}
		if err := fdw[2*i+1].Close(); err != nil {
			t.Fatalf("Failed to close fdw[%d]: %v", 2*i+1, err)
		}
		rmFifo(t, 2*i)
		rmFifo(t, 2*i+1)
	}
}
<commit_msg>echo_test.go: Trivial Efforf format-string fixes<commit_after>// Copyright (c) 2014, Nick Patavalis (npat@efault.net).
// All rights reserved.
// Use of this source code is governed by a BSD-style license that can
// be found in the LICENSE.txt file.

/*

Create nPairs pairs of goroutines, and nPairs * 2 fifos. In every
goroutine pair, one goroutine is the Echoer and the other is the
Sender. They are connected by fifos like this:

  [ echoer ] <-fdr[2*i]------ fifo 2*i --------fdw[2*i]-- [ sender ]
  [   #i   ] --fdw[2*i+1]---- fifo 2*i+1 ----fdr[2*i+1]-> [   #i   ]

The Senders send messages (nMsg each) to the respective Echoers. The
Echoers echo the received messages back to the senders. The senders
check if the echoed-back messages are identical to the originals.

*/

package poller_test

import (
	"bytes"
	"fmt"
	"io"
	"math/rand"
	"runtime"
	"syscall"
	"testing"
	"time"

	"github.com/npat-efault/poller"
)

const (
	nPairs = 8   // Number of Sender / Echoer pairs
	nMsg   = 100 // Number of messages sent to each Echoer
)

// !! ATTENTION !! We unconditionally delete files named like this !!
const fifo = "/tmp/poller-test-fifo" // /tmp/poller-test-fifoXXX

func packUInt32BE(p []byte, i uint32) []byte {
	return append(p,
		byte(i>>24),
		byte(i>>16),
		byte(i>>8),
		byte(i))
}

func packUInt16BE(p []byte, i uint16) []byte {
	return append(p,
		byte(i>>8),
		byte(i))
}

func unpackUInt32BE(p []byte) (uint32, []byte) {
	i := uint32(p[0])<<24 |
		uint32(p[1])<<16 |
		uint32(p[2])<<8 |
		uint32(p[3])
	return i, p[4:]
}

func unpackUInt16BE(p []byte) (uint16, []byte) {
	i := uint16(p[0])<<8 |
		uint16(p[1])
	return i, p[2:]
}

func packRandData(p []byte, n int) []byte {
	// This is *very* slow due to the repetitive locking /
	// unlocking of the common random source.
	for i := 0; i < n; i++ {
		p = append(p, byte(rand.Intn(0x100)))
	}
	return p
}

// Message (msg):
//
// +--+--+--+--+--+--+--+-- .... ... +--+
// | len |    seq    |       data       |
// +--+--+--+--+--+--+--+-- .... ... +--+
// |                 |                  |
// |<--headSz (6)--->|<--len - headSz-->|

const (
	lenSz  = 2
	seqSz  = 4
	headSz = lenSz + seqSz
)

type msg []byte

func randMsg(seq uint32) msg {
	n := rand.Intn(0x10000 - headSz)
	l := headSz + n
	m := make(msg, 0, l)
	m = packUInt16BE(m, uint16(l))
	m = packUInt32BE(m, seq)
	m = packRandData(m, n)
	return m
}

func (m msg) Len() int {
	n, _ := unpackUInt16BE(m)
	return int(n)
}

func (m msg) Seq() uint32 {
	sq, _ := unpackUInt32BE(m[lenSz:])
	return sq
}

func (m msg) Data() []byte {
	return m[headSz:]
}

func (m msg) Send(w io.Writer) error {
	_, err := w.Write(m)
	return err
}

func rcvMsg(r io.Reader) (msg, error) {
	m0 := make(msg, lenSz)
	_, err := io.ReadFull(r, m0)
	if err != nil {
		return nil, err
	}
	l := m0.Len()
	m := make(msg, l)
	copy(m, m0)
	_, err = io.ReadFull(r, m[lenSz:])
	if err != nil {
		return nil, err
	}
	return m, nil
}

func sendN(r, w *poller.FD, n int) error {
	var err error
	for i := 0; i < n; i++ {
		var ms, mr msg
		ms = randMsg(uint32(i))
		err = w.SetWriteDeadline(time.Now().Add(2 * time.Second))
		if err != nil {
			err = fmt.Errorf("set W dl #%d: %v", i, err)
			break
		}
		err = ms.Send(w)
		if err != nil {
			err = fmt.Errorf("cannot send #%d: %v", i, err)
			break
		}
		err = r.SetReadDeadline(time.Now().Add(2 * time.Second))
		if err != nil {
			err = fmt.Errorf("set R dl #%d: %v", i, err)
			break
		}
		mr, err = rcvMsg(r)
		if err != nil {
			err = fmt.Errorf("cannot rcv #%d: %v", i, err)
			break
		}
		if mr.Seq() != uint32(i) {
			err = fmt.Errorf("rcv seq %d != %d", mr.Seq(), i)
			break
		}
		if !bytes.Equal(ms.Data(), mr.Data()) {
			err = fmt.Errorf("data not equal for #%d", i)
			break
		}
	}
	return err
}

func echoN(r, w *poller.FD, n int) error {
	var err error
	for i := 0; i < n; i++ {
		var m msg
		err = r.SetReadDeadline(time.Now().Add(2 * time.Second))
		if err != nil {
			err = fmt.Errorf("set R dl #%d: %v", i, err)
			break
		}
		m, err = rcvMsg(r)
		if err != nil {
			err = fmt.Errorf("cannot rcv #%d: %v", i, err)
			break
		}
		if m.Seq() != uint32(i) {
			err = fmt.Errorf("rcv seq %d != %d", m.Seq(), i)
			break
		}
		err = w.SetWriteDeadline(time.Now().Add(2 * time.Second))
		if err != nil {
			err = fmt.Errorf("set W dl #%d: %v", i, err)
			break
		}
		err = m.Send(w)
		if err != nil {
			err = fmt.Errorf("cannot send #%d: %v", i, err)
			break
		}
	}
	return err
}

func fifoName(i int) string {
	return fifo + fmt.Sprintf("%03d", i)
}

func mkFifo(t *testing.T, i int) {
	name := fifoName(i)
	syscall.Unlink(name)
	err := syscall.Mkfifo(name, 0666)
	if err != nil {
		t.Fatalf("mkFifo %s: %v", name, err)
	}
}

func rmFifo(t *testing.T, i int) {
	name := fifoName(i)
	err := syscall.Unlink(name)
	if err != nil {
		t.Fatalf("rmFifo %s: %v", name, err)
	}
}

func openFifo(t *testing.T, i int, read bool) *poller.FD {
	name := fifoName(i)
	flags := poller.O_RO
	if !read {
		flags = poller.O_WO
	}
	fd, err := poller.Open(name, flags)
	if err != nil {
		t.Fatalf("Open %s: %v", name, err)
	}
	return fd
}

func waitNTmo(t *testing.T, ch <-chan error, n int, d time.Duration) {
	tmo := time.After(d)
	for i := 0; i < n; i++ {
		select {
		case err := <-ch:
			if err != nil {
				_, _, line, _ := runtime.Caller(1)
				t.Fatalf("%d: %v", line, err)
			}
		case <-tmo:
			_, _, line, _ := runtime.Caller(1)
			t.Fatalf("%d: Timeout!", line)
		}
	}
}

func TestEcho(t *testing.T) {
	fdr := make([]*poller.FD, nPairs*2)
	fdw := make([]*poller.FD, nPairs*2)
	for i := 0; i < nPairs; i++ {
		// even fifos: echoers read, senders write
		// odd fifos: echoes write, senders read
		mkFifo(t, 2*i)
		mkFifo(t, 2*i+1)
		fdr[2*i] = openFifo(t, 2*i, true)
		fdw[2*i] = openFifo(t, 2*i, false)
		fdr[2*i+1] = openFifo(t, 2*i+1, true)
		fdw[2*i+1] = openFifo(t, 2*i+1, false)
	}

	ch := make(chan error)

	for i := 0; i < nPairs; i++ {
		go func(i int) {
			err := echoN(fdr[2*i], fdw[2*i+1], nMsg)
			if err != nil {
				err = fmt.Errorf("Echoer %d: %v", i, err)
			}
			ch <- err
		}(i)
		go func(i int) {
			err := sendN(fdr[2*i+1], fdw[2*i], nMsg)
			if err != nil {
				err = fmt.Errorf("Sender %d: %v", i, err)
			}
			ch <- err
		}(i)
	}
	waitNTmo(t, ch, nPairs*2, 60*time.Second)

	for i := 0; i < nPairs; i++ {
		if err := fdr[2*i].Close(); err != nil {
			t.Fatalf("Failed to close fdr[%d]: %v", 2*i, err)
		}
		if err := fdw[2*i].Close(); err != nil {
			t.Fatalf("Failed to close fdw[%d]: %v", 2*i, err)
		}
		if err := fdr[2*i+1].Close(); err != nil {
			t.Fatalf("Failed to close fdr[%d]: %v", 2*i+1, err)
		}
		if err := fdw[2*i+1].Close(); err != nil {
			t.Fatalf("Failed to close fdw[%d]: %v", 2*i+1, err)
		}
		rmFifo(t, 2*i)
		rmFifo(t, 2*i+1)
	}
}
<|endoftext|>


<commit_before>package nodos

import (
	"fmt"
	"testing"
)

func _testJoin(a, b string) {
	fmt.Printf("'%s'+'%s' -> '%s'\n", a, b, Join(a, b))
}

func TestJoin(t *testing.T) {
	_testJoin(`foo`, `bar`)
	_testJoin(`foo`, `\bar`)
	_testJoin(`c:`, `bar`)
	_testJoin(`foo/`, `bar`)
	_testJoin(`foo\`, `bar`)
	_testJoin(`foo`, `c:bar`)
	_testJoin(`foo`, `c:\bar`)
	_testJoin(`c:foo`, `\bar`)
	_testJoin(`c:foo`, `\\host\path\to`)
	_testJoin(`\\host\path\to`, `c:foo`)
}
<commit_msg>Wrote unit-test for "nodos".Join<commit_after>package nodos

import (
	"testing"
)

func _testJoin(t *testing.T, a, b, expect string) {
	result := Join(a, b)
	if result != expect {
		t.Fatalf("'%s'+'%s' should be '%s',but '%s'\n", a, b, expect, result)
	}
}

func TestJoin(t *testing.T) {
	_testJoin(t, `foo`, `bar`, `foo\bar`)
	_testJoin(t, `foo`, `\bar`, `\bar`)
	_testJoin(t, `c:`, `bar`, `c:bar`)
	_testJoin(t, `foo/`, `bar`, `foo/bar`)
	_testJoin(t, `foo\`, `bar`, `foo\bar`)
	_testJoin(t, `foo`, `c:bar`, `c:bar`)
	_testJoin(t, `foo`, `c:\bar`, `c:\bar`)
	_testJoin(t, `c:foo`, `\bar`, `c:\bar`)
	_testJoin(t, `c:foo`, `\\host\path\to`, `\\host\path\to`)
	_testJoin(t, `\\host\path\to`, `c:foo`, `c:foo`)
}
<|endoftext|>