<commit_before>package registry

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/cozy/echo"
	"github.com/cozy/httpcache"
)

const defaultLimit = 50

// A Version describes a specific release of an application.
type Version struct {
	Slug      string          `json:"slug"`
	Version   string          `json:"version"`
	URL       string          `json:"url"`
	Sha256    string          `json:"sha256"`
	CreatedAt time.Time       `json:"created_at"`
	Size      string          `json:"size"`
	Manifest  json.RawMessage `json:"manifest"`
	TarPrefix string          `json:"tar_prefix"`
}

var errVersionNotFound = errors.New("Version not found")

var proxyClient = &http.Client{
	Timeout:   10 * time.Second,
	Transport: httpcache.NewMemoryCacheTransport(32),
}

// CacheControl defines whether or not to use caching for the request made to
// the registries.
type CacheControl int

const (
	// WithCache specify caching
	WithCache CacheControl = iota
	// NoCache disables any caching
	NoCache
)

// GetLatestVersion returns the latest version available from the list of
// registries by resolving them in sequence using the specified application
// slug and channel name.
func GetLatestVersion(slug, channel string, registries []*url.URL) (*Version, error) {
	requestURI := fmt.Sprintf("/registry/%s/%s/latest",
		url.PathEscape(slug),
		url.PathEscape(channel))
	resp, ok, err := fetchUntilFound(registries, requestURI, WithCache)
	if err != nil {
		return nil, err
	}
	if !ok {
		return nil, errVersionNotFound
	}
	defer resp.Body.Close()
	var v *Version
	if err = json.NewDecoder(resp.Body).Decode(&v); err != nil {
		return nil, err
	}
	return v, nil
}

// Proxy will proxy the given request to the registries in sequence and return
// the response as io.ReadCloser when finding a registry returning a HTTP 200OK
// response.
func Proxy(req *http.Request, registries []*url.URL, cache CacheControl) (*http.Response, error) {
	resp, ok, err := fetchUntilFound(registries, req.RequestURI, cache)
	if err != nil {
		return nil, err
	}
	if !ok {
		return nil, echo.NewHTTPError(http.StatusNotFound)
	}
	return resp, nil
}

// ProxyList will proxy the given request to the registries by aggregating the
// results along the way. It should be used for list endpoints.
func ProxyList(req *http.Request, registries []*url.URL) (json.RawMessage, error) {
	ref, err := url.Parse(req.RequestURI)
	if err != nil {
		return nil, err
	}

	var sortBy string
	var sortReverse bool
	var limit int

	cursors := make([]int, len(registries))

	q := ref.Query()
	if v, ok := q["cursor"]; ok {
		splits := strings.Split(v[0], "|")
		for i, s := range splits {
			if i >= len(registries) {
				break
			}
			cursors[i], _ = strconv.Atoi(s)
		}
	}
	if v, ok := q["sort"]; ok {
		sortBy = v[0]
	}
	if len(sortBy) > 0 && sortBy[0] == '-' {
		sortReverse = true
		sortBy = sortBy[1:]
	}
	if sortBy == "" {
		sortBy = "slug"
	}
	if v, ok := q["limit"]; ok {
		limit, _ = strconv.Atoi(v[0])
	}
	if limit <= 0 {
		limit = defaultLimit
	}

	list := newAppsList(ref, registries, cursors, limit)
	if err := list.FetchAll(); err != nil {
		return nil, err
	}
	return json.Marshal(list.Paginated(sortBy, sortReverse, limit))
}

type jsonObject map[string]interface{}

type appsList struct {
	ref        *url.URL
	list       []jsonObject
	registries []*registryFetchState
	slugs      map[string][]int
	limit      int
}

type pageInfo struct {
	Count      int    `json:"count"`
	NextCursor string `json:"next_cursor,omitempty"`
}

type appsPaginated struct {
	List     []jsonObject `json:"data"`
	PageInfo pageInfo     `json:"meta"`
}

type registryFetchState struct {
	url    *url.URL
	index  int // index in the registries array
	cursor int // cursor used to fetch the registry
	ended  int // cursor of the last element in the regitry (-1 if unknown)
}

func newAppsList(ref *url.URL, registries []*url.URL, cursors []int, limit int) *appsList {
	if len(registries) != len(cursors) {
		panic("should have same length")
	}
	regStates := make([]*registryFetchState, len(registries))
	for i := range regStates {
		regStates[i] = &registryFetchState{
			index:  i,
			url:    registries[i],
			cursor: cursors[i],
			ended:  -1,
		}
	}
	return &appsList{
		ref:        ref,
		limit:      limit,
		list:       make([]jsonObject, 0),
		slugs:      make(map[string][]int),
		registries: regStates,
	}
}

func (a *appsList) FetchAll() error {
	l := len(a.registries)
	for i, r := range a.registries {
		// We fetch the entire registry except for the last one. In practice, the
		// "high-priority" registries should be small and the last one contain the
		// vast majority of the applications.
		fetchAll := i < l-1
		if err := a.fetch(r, fetchAll); err != nil {
			return err
		}
	}
	return nil
}

func (a *appsList) fetch(r *registryFetchState, fetchAll bool) error {
	slugs := a.slugs
	minCursor := r.cursor
	maxCursor := r.cursor + a.limit

	var cursor, limit int
	if fetchAll {
		cursor = 0
		limit = 50
	} else {
		cursor = r.cursor
		limit = a.limit
	}

	// A negative dimension of the cursor means we already reached the end of the
	// list. There is no need to fetch anymore in that case.
	if !fetchAll && r.cursor < 0 {
		return nil
	}

	added := 0
	for {
		ref := addQueries(removeQueries(a.ref, "cursor", "limit"),
			"cursor", strconv.Itoa(cursor),
			"limit", strconv.Itoa(limit),
		)
		resp, ok, err := fetch(r.url, ref, NoCache)
		if err != nil {
			return err
		}
		if !ok {
			return nil
		}
		defer resp.Body.Close()
		var page appsPaginated
		if err = json.NewDecoder(resp.Body).Decode(&page); err != nil {
			return err
		}

		for i, obj := range page.List {
			objCursor := cursor + i

			objInRange := r.cursor >= 0 &&
				objCursor >= minCursor &&
				objCursor <= maxCursor

			// if an object with same slug has already been fetched, we skip it
			slug := obj["slug"].(string)
			offsets, ok := slugs[slug]
			if !ok {
				offsets = make([]int, len(a.registries))
				slugs[slug] = offsets
			}
			if objInRange {
				offsets[r.index] = objCursor + 1
				if !ok {
					a.list = append(a.list, obj)
					added++
				}
			}
		}

		nextCursor := page.PageInfo.NextCursor
		if nextCursor == "" {
			r.ended = cursor + len(page.List)
			break
		}

		cursor, _ = strconv.Atoi(nextCursor)
		if !fetchAll && limit-added <= 0 {
			break
		}
	}

	return nil
}

func (a *appsList) Paginated(sortBy string, reverse bool, limit int) *appsPaginated {
	sortBySlug := sortBy == "slug"
	sort.Slice(a.list, func(i, j int) bool {
		vi := a.list[i]
		vj := a.list[j]
		var less, equal bool
		switch valA := vi[sortBy].(type) {
		case string:
			valB := vj[sortBy].(string)
			less = valA < valB
			if !sortBySlug && !less {
				equal = valA == valB
			}
		case int:
			valB := vj[sortBy].(int)
			less = valA < valB
			if !sortBySlug && !less {
				equal = valA == valB
			}
		}
		if equal {
			slugA := vi["slug"].(string)
			slugB := vj["slug"].(string)
			less = slugA < slugB
		}
		if reverse {
			return !less
		}
		return less
	})

	if limit > len(a.list) {
		limit = len(a.list)
	}

	list := a.list[:limit]

	// Copy the original cursor
	cursors := make([]int, len(a.registries))
	for i, reg := range a.registries {
		cursors[i] = reg.cursor
	}

	// Calculation of the next multi-cursor by iterating through the sorted and
	// truncated list and incrementing the dimension of the multi-cursor
	// associated with the objects registry.
	//
	// In the end, we also check if the end value of each dimensions of the
	// cursor reached the end of the list. If so, the dimension is set to -1.
	l := len(a.registries)
	for _, o := range list {
		slug := o["slug"].(string)
		offsets := a.slugs[slug]

		i := 0
		// This first loop checks the first element >= 0 in the offsets associated
		// to the object. This first non null element is set as the cursor of the
		// dimension.
		for ; i < l; i++ {
			if c := offsets[i]; c > 0 {
				cursors[i] = c
				break
			}
		}
		// We continue the iteration to the next lower-priority dimensions and for
		// non-null ones, we can increment their value by at-most one. This
		// correspond to values that where rejected by having the same slugs as
		// prioritized objects.
		i++
		for ; i < l; i++ {
			if c := offsets[i]; c > 0 && cursors[i] == c-1 {
				cursors[i] = c
			}
		}
	}

	for i, reg := range a.registries {
		if e := reg.ended; e >= 0 && cursors[i] >= e {
			cursors[i] = -1
		}
	}

	return &appsPaginated{
		List: list,
		PageInfo: pageInfo{
			Count:      len(list),
			NextCursor: printMutliCursor(cursors),
		},
	}
}

func fetchUntilFound(registries []*url.URL, requestURI string, cache CacheControl) (resp *http.Response, ok bool, err error) {
	ref, err := url.Parse(requestURI)
	if err != nil {
		return
	}
	for _, registry := range registries {
		resp, ok, err = fetch(registry, ref, cache)
		if err != nil {
			return
		}
		if !ok {
			continue
		}
		return
	}
	return nil, false, nil
}

func fetch(registry, ref *url.URL, cache CacheControl) (resp *http.Response, ok bool, err error) {
	u := registry.ResolveReference(ref)
	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
	if err != nil {
		return
	}
	if cache == NoCache {
		req.Header.Set("cache-control", "no-cache")
	}
	resp, err = proxyClient.Do(req)
	if err != nil {
		return
	}
	defer func() {
		if err != nil {
			resp.Body.Close()
		}
	}()
	if resp.StatusCode == 404 {
		return
	}
	if resp.StatusCode != 200 {
		var msg struct {
			Message string `json:"message"`
		}
		if err = json.NewDecoder(resp.Body).Decode(&msg); err != nil {
			err = echo.NewHTTPError(resp.StatusCode)
		} else {
			err = echo.NewHTTPError(resp.StatusCode, msg.Message)
		}
		return
	}
	return resp, true, nil
}

func printMutliCursor(c []int) string {
	// if all dimensions of the multi-cursor are -1, we print the empty string
	sum := 0
	for _, i := range c {
		sum += i
	}
	if sum == -len(c) {
		return ""
	}
	var a []string
	for _, i := range c {
		a = append(a, strconv.Itoa(i))
	}
	return strings.Join(a, "|")
}

func removeQueries(u *url.URL, filter ...string) *url.URL {
	u, _ = url.Parse(u.String())
	q1 := u.Query()
	q2 := make(url.Values)
	for k, v := range q1 {
		if len(v) == 0 {
			continue
		}
		var remove bool
		for _, f := range filter {
			if f == k {
				remove = true
				break
			}
		}
		if !remove {
			q2.Add(k, v[0])
		}
	}
	u.RawQuery = q2.Encode()
	return u
}

func addQueries(u *url.URL, queries ...string) *url.URL {
	u, _ = url.Parse(u.String())
	q := u.Query()
	for i := 0; i < len(queries); i += 2 {
		q.Add(queries[i], queries[i+1])
	}
	u.RawQuery = q.Encode()
	return u
}
<commit_msg>Fix resolving path on registry<commit_after>package registry

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"path"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/cozy/echo"
	"github.com/cozy/httpcache"
)

const defaultLimit = 50

// A Version describes a specific release of an application.
type Version struct {
	Slug      string          `json:"slug"`
	Version   string          `json:"version"`
	URL       string          `json:"url"`
	Sha256    string          `json:"sha256"`
	CreatedAt time.Time       `json:"created_at"`
	Size      string          `json:"size"`
	Manifest  json.RawMessage `json:"manifest"`
	TarPrefix string          `json:"tar_prefix"`
}

var errVersionNotFound = errors.New("Version not found")

var proxyClient = &http.Client{
	Timeout:   10 * time.Second,
	Transport: httpcache.NewMemoryCacheTransport(32),
}

// CacheControl defines whether or not to use caching for the request made to
// the registries.
type CacheControl int

const (
	// WithCache specify caching
	WithCache CacheControl = iota
	// NoCache disables any caching
	NoCache
)

// GetLatestVersion returns the latest version available from the list of
// registries by resolving them in sequence using the specified application
// slug and channel name.
func GetLatestVersion(slug, channel string, registries []*url.URL) (*Version, error) {
	requestURI := fmt.Sprintf("/registry/%s/%s/latest",
		url.PathEscape(slug),
		url.PathEscape(channel))
	resp, ok, err := fetchUntilFound(registries, requestURI, WithCache)
	if err != nil {
		return nil, err
	}
	if !ok {
		return nil, errVersionNotFound
	}
	defer resp.Body.Close()
	var v *Version
	if err = json.NewDecoder(resp.Body).Decode(&v); err != nil {
		return nil, err
	}
	return v, nil
}

// Proxy will proxy the given request to the registries in sequence and return
// the response as io.ReadCloser when finding a registry returning a HTTP 200OK
// response.
func Proxy(req *http.Request, registries []*url.URL, cache CacheControl) (*http.Response, error) {
	resp, ok, err := fetchUntilFound(registries, req.RequestURI, cache)
	if err != nil {
		return nil, err
	}
	if !ok {
		return nil, echo.NewHTTPError(http.StatusNotFound)
	}
	return resp, nil
}

// ProxyList will proxy the given request to the registries by aggregating the
// results along the way. It should be used for list endpoints.
func ProxyList(req *http.Request, registries []*url.URL) (json.RawMessage, error) {
	ref, err := url.Parse(req.RequestURI)
	if err != nil {
		return nil, err
	}

	var sortBy string
	var sortReverse bool
	var limit int

	cursors := make([]int, len(registries))

	q := ref.Query()
	if v, ok := q["cursor"]; ok {
		splits := strings.Split(v[0], "|")
		for i, s := range splits {
			if i >= len(registries) {
				break
			}
			cursors[i], _ = strconv.Atoi(s)
		}
	}
	if v, ok := q["sort"]; ok {
		sortBy = v[0]
	}
	if len(sortBy) > 0 && sortBy[0] == '-' {
		sortReverse = true
		sortBy = sortBy[1:]
	}
	if sortBy == "" {
		sortBy = "slug"
	}
	if v, ok := q["limit"]; ok {
		limit, _ = strconv.Atoi(v[0])
	}
	if limit <= 0 {
		limit = defaultLimit
	}

	list := newAppsList(ref, registries, cursors, limit)
	if err := list.FetchAll(); err != nil {
		return nil, err
	}
	return json.Marshal(list.Paginated(sortBy, sortReverse, limit))
}

type jsonObject map[string]interface{}

type appsList struct {
	ref        *url.URL
	list       []jsonObject
	registries []*registryFetchState
	slugs      map[string][]int
	limit      int
}

type pageInfo struct {
	Count      int    `json:"count"`
	NextCursor string `json:"next_cursor,omitempty"`
}

type appsPaginated struct {
	List     []jsonObject `json:"data"`
	PageInfo pageInfo     `json:"meta"`
}

type registryFetchState struct {
	url    *url.URL
	index  int // index in the registries array
	cursor int // cursor used to fetch the registry
	ended  int // cursor of the last element in the regitry (-1 if unknown)
}

func newAppsList(ref *url.URL, registries []*url.URL, cursors []int, limit int) *appsList {
	if len(registries) != len(cursors) {
		panic("should have same length")
	}
	regStates := make([]*registryFetchState, len(registries))
	for i := range regStates {
		regStates[i] = &registryFetchState{
			index:  i,
			url:    registries[i],
			cursor: cursors[i],
			ended:  -1,
		}
	}
	return &appsList{
		ref:        ref,
		limit:      limit,
		list:       make([]jsonObject, 0),
		slugs:      make(map[string][]int),
		registries: regStates,
	}
}

func (a *appsList) FetchAll() error {
	l := len(a.registries)
	for i, r := range a.registries {
		// We fetch the entire registry except for the last one. In practice, the
		// "high-priority" registries should be small and the last one contain the
		// vast majority of the applications.
		fetchAll := i < l-1
		if err := a.fetch(r, fetchAll); err != nil {
			return err
		}
	}
	return nil
}

func (a *appsList) fetch(r *registryFetchState, fetchAll bool) error {
	slugs := a.slugs
	minCursor := r.cursor
	maxCursor := r.cursor + a.limit

	var cursor, limit int
	if fetchAll {
		cursor = 0
		limit = 50
	} else {
		cursor = r.cursor
		limit = a.limit
	}

	// A negative dimension of the cursor means we already reached the end of the
	// list. There is no need to fetch anymore in that case.
	if !fetchAll && r.cursor < 0 {
		return nil
	}

	added := 0
	for {
		ref := addQueries(removeQueries(a.ref, "cursor", "limit"),
			"cursor", strconv.Itoa(cursor),
			"limit", strconv.Itoa(limit),
		)
		resp, ok, err := fetch(r.url, ref, NoCache)
		if err != nil {
			return err
		}
		if !ok {
			return nil
		}
		defer resp.Body.Close()
		var page appsPaginated
		if err = json.NewDecoder(resp.Body).Decode(&page); err != nil {
			return err
		}

		for i, obj := range page.List {
			objCursor := cursor + i

			objInRange := r.cursor >= 0 &&
				objCursor >= minCursor &&
				objCursor <= maxCursor

			// if an object with same slug has already been fetched, we skip it
			slug := obj["slug"].(string)
			offsets, ok := slugs[slug]
			if !ok {
				offsets = make([]int, len(a.registries))
				slugs[slug] = offsets
			}
			if objInRange {
				offsets[r.index] = objCursor + 1
				if !ok {
					a.list = append(a.list, obj)
					added++
				}
			}
		}

		nextCursor := page.PageInfo.NextCursor
		if nextCursor == "" {
			r.ended = cursor + len(page.List)
			break
		}

		cursor, _ = strconv.Atoi(nextCursor)
		if !fetchAll && limit-added <= 0 {
			break
		}
	}

	return nil
}

func (a *appsList) Paginated(sortBy string, reverse bool, limit int) *appsPaginated {
	sortBySlug := sortBy == "slug"
	sort.Slice(a.list, func(i, j int) bool {
		vi := a.list[i]
		vj := a.list[j]
		var less, equal bool
		switch valA := vi[sortBy].(type) {
		case string:
			valB := vj[sortBy].(string)
			less = valA < valB
			if !sortBySlug && !less {
				equal = valA == valB
			}
		case int:
			valB := vj[sortBy].(int)
			less = valA < valB
			if !sortBySlug && !less {
				equal = valA == valB
			}
		}
		if equal {
			slugA := vi["slug"].(string)
			slugB := vj["slug"].(string)
			less = slugA < slugB
		}
		if reverse {
			return !less
		}
		return less
	})

	if limit > len(a.list) {
		limit = len(a.list)
	}

	list := a.list[:limit]

	// Copy the original cursor
	cursors := make([]int, len(a.registries))
	for i, reg := range a.registries {
		cursors[i] = reg.cursor
	}

	// Calculation of the next multi-cursor by iterating through the sorted and
	// truncated list and incrementing the dimension of the multi-cursor
	// associated with the objects registry.
	//
	// In the end, we also check if the end value of each dimensions of the
	// cursor reached the end of the list. If so, the dimension is set to -1.
	l := len(a.registries)
	for _, o := range list {
		slug := o["slug"].(string)
		offsets := a.slugs[slug]

		i := 0
		// This first loop checks the first element >= 0 in the offsets associated
		// to the object. This first non null element is set as the cursor of the
		// dimension.
		for ; i < l; i++ {
			if c := offsets[i]; c > 0 {
				cursors[i] = c
				break
			}
		}
		// We continue the iteration to the next lower-priority dimensions and for
		// non-null ones, we can increment their value by at-most one. This
		// correspond to values that where rejected by having the same slugs as
		// prioritized objects.
		i++
		for ; i < l; i++ {
			if c := offsets[i]; c > 0 && cursors[i] == c-1 {
				cursors[i] = c
			}
		}
	}

	for i, reg := range a.registries {
		if e := reg.ended; e >= 0 && cursors[i] >= e {
			cursors[i] = -1
		}
	}

	return &appsPaginated{
		List: list,
		PageInfo: pageInfo{
			Count:      len(list),
			NextCursor: printMutliCursor(cursors),
		},
	}
}

func fetchUntilFound(registries []*url.URL, requestURI string, cache CacheControl) (resp *http.Response, ok bool, err error) {
	ref, err := url.Parse(requestURI)
	if err != nil {
		return
	}
	for _, registry := range registries {
		resp, ok, err = fetch(registry, ref, cache)
		if err != nil {
			return
		}
		if !ok {
			continue
		}
		return
	}
	return nil, false, nil
}

func fetch(registry, ref *url.URL, cache CacheControl) (resp *http.Response, ok bool, err error) {
	u := registry.ResolveReference(ref)
	u.Path = path.Join(registry.Path, ref.Path)
	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
	if err != nil {
		return
	}
	if cache == NoCache {
		req.Header.Set("cache-control", "no-cache")
	}
	resp, err = proxyClient.Do(req)
	if err != nil {
		return
	}
	defer func() {
		if err != nil {
			resp.Body.Close()
		}
	}()
	if resp.StatusCode == 404 {
		return
	}
	if resp.StatusCode != 200 {
		var msg struct {
			Message string `json:"message"`
		}
		if err = json.NewDecoder(resp.Body).Decode(&msg); err != nil {
			err = echo.NewHTTPError(resp.StatusCode)
		} else {
			err = echo.NewHTTPError(resp.StatusCode, msg.Message)
		}
		return
	}
	return resp, true, nil
}

func printMutliCursor(c []int) string {
	// if all dimensions of the multi-cursor are -1, we print the empty string
	sum := 0
	for _, i := range c {
		sum += i
	}
	if sum == -len(c) {
		return ""
	}
	var a []string
	for _, i := range c {
		a = append(a, strconv.Itoa(i))
	}
	return strings.Join(a, "|")
}

func removeQueries(u *url.URL, filter ...string) *url.URL {
	u, _ = url.Parse(u.String())
	q1 := u.Query()
	q2 := make(url.Values)
	for k, v := range q1 {
		if len(v) == 0 {
			continue
		}
		var remove bool
		for _, f := range filter {
			if f == k {
				remove = true
				break
			}
		}
		if !remove {
			q2.Add(k, v[0])
		}
	}
	u.RawQuery = q2.Encode()
	return u
}

func addQueries(u *url.URL, queries ...string) *url.URL {
	u, _ = url.Parse(u.String())
	q := u.Query()
	for i := 0; i < len(queries); i += 2 {
		q.Add(queries[i], queries[i+1])
	}
	u.RawQuery = q.Encode()
	return u
}
<|endoftext|>


<commit_before>/*
Copyright 2011 Google 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 schema

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"strings"
	"sync"

	"camlistore.org/pkg/blobref"
	"camlistore.org/pkg/singleflight"
)

var _ = log.Printf

const closedIndex = -1

var errClosed = errors.New("filereader is closed")

// A FileReader reads the bytes of "file" and "bytes" schema blobrefs.
type FileReader struct {
	*io.SectionReader

	ssmmu sync.Mutex           // guards ssm
	ssm   map[string]*Superset // blobref -> superset

	sfg singleflight.Group // for loading blobrefs for ssm

	fetcher blobref.SeekFetcher
	ss      *Superset

	size int64 // total number of bytes
}

// NewFileReader returns a new FileReader reading the contents of fileBlobRef,
// fetching blobs from fetcher.  The fileBlobRef must be of a "bytes" or "file"
// schema blob.
//
// The caller should call Close on the FileReader when done reading.
func NewFileReader(fetcher blobref.SeekFetcher, fileBlobRef *blobref.BlobRef) (*FileReader, error) {
	// TODO(bradfitz): make this take a blobref.FetcherAt instead?
	// TODO(bradfitz): rename this into bytes reader? but for now it's still
	//                 named FileReader, but can also read a "bytes" schema.
	if fileBlobRef == nil {
		return nil, errors.New("schema/filereader: NewFileReader blobref was nil")
	}
	rsc, _, err := fetcher.Fetch(fileBlobRef)
	if err != nil {
		return nil, fmt.Errorf("schema/filereader: fetching file schema blob: %v", err)
	}
	defer rsc.Close()
	ss, err := ParseSuperset(rsc)
	if err != nil {
		return nil, fmt.Errorf("schema/filereader: decoding file schema blob: %v", err)
	}
	if ss.Type != "file" && ss.Type != "bytes" {
		return nil, fmt.Errorf("schema/filereader: expected \"file\" or \"bytes\" schema blob, got %q", ss.Type)
	}
	fr, err := ss.NewFileReader(fetcher)
	if err != nil {
		return nil, fmt.Errorf("schema/filereader: creating FileReader for %s: %v", fileBlobRef, err)
	}
	return fr, nil
}

// NewFileReader returns a new FileReader, reading bytes and blobs
// from the provided fetcher.
//
// NewFileReader does no fetch operation on the fetcher itself.  The
// fetcher is only used in subsequent read operations.
//
// An error is only returned if the type of the Superset is not either
// "file" or "bytes".
func (ss *Superset) NewFileReader(fetcher blobref.SeekFetcher) (*FileReader, error) {
	if ss.Type != "file" && ss.Type != "bytes" {
		return nil, fmt.Errorf("schema/filereader: Superset not of type \"file\" or \"bytes\"")
	}
	size := int64(ss.SumPartsSize())
	fr := &FileReader{
		fetcher: fetcher,
		ss:      ss,
		size:    size,
		ssm:     make(map[string]*Superset),
	}
	fr.SectionReader = io.NewSectionReader(fr, 0, size)
	return fr, nil
}

// LoadAllChunks causes all chunks of the file to be loaded as quickly
// as possible.  The contents are immediately discarded, so it is
// assumed that the fetcher is a caching fetcher.
func (fr *FileReader) LoadAllChunks() {
	offsetc := make(chan int64, 16)
	go func() {
		for off := range offsetc {
			go func(off int64) {
				rc, err := fr.readerForOffset(off)
				if err == nil {
					rc.Close()
				}
			}(off)
		}
	}()
	go fr.GetChunkOffsets(offsetc)
}

// FileSchema returns the reader's schema superset. Don't mutate it.
func (fr *FileReader) FileSchema() *Superset {
	return fr.ss
}

func (fr *FileReader) Close() error {
	// TODO: close cached blobs?
	return nil
}

var _ interface {
	io.ReaderAt
	io.Reader
	io.Closer
	Size() int64
} = (*FileReader)(nil)

func (fr *FileReader) ReadAt(p []byte, offset int64) (n int, err error) {
	if offset < 0 {
		return 0, errors.New("schema/filereader: negative offset")
	}
	if offset >= fr.Size() {
		return 0, io.EOF
	}
	want := len(p)
	for len(p) > 0 && err == nil {
		var rc io.ReadCloser
		rc, err = fr.readerForOffset(offset)
		if err != nil {
			return
		}
		var n1 int64 // never bigger than an int
		n1, err = io.CopyN(&sliceWriter{p}, rc, int64(len(p)))
		rc.Close()
		if err == io.EOF {
			err = nil
		}
		if n1 == 0 {
			break
		}
		p = p[n1:]
		offset += int64(n1)
		n += int(n1)
	}
	if n < want && err == nil {
		err = io.ErrUnexpectedEOF
	}
	return n, err
}

// GetChunkOffsets sends c each of the file's chunk offsets.
// The offsets are not necessarily sent in order, and all ranges of the file
// are not necessarily represented if the file contains zero holes.
// The channel c is closed before the function returns, regardless of error.
func (fr *FileReader) GetChunkOffsets(c chan<- int64) error {
	defer close(c)
	return fr.sendPartsChunks(c, 0, fr.ss.Parts)
}

func (fr *FileReader) sendPartsChunks(c chan<- int64, off int64, parts []*BytesPart) error {
	var errcs []chan error
	for _, p := range parts {
		switch {
		case p.BlobRef != nil && p.BytesRef != nil:
			return fmt.Errorf("part illegally contained both a blobRef and bytesRef")
		case p.BlobRef == nil && p.BytesRef == nil:
			// Don't send
			break
		case p.BlobRef != nil:
			c <- off
		case p.BytesRef != nil:
			errc := make(chan error, 1)
			errcs = append(errcs, errc)
			br := p.BytesRef
			offNow := off
			go func() {
				ss, err := fr.getSuperset(br)
				if err != nil {
					errc <- err
					return
				}
				fr.sendPartsChunks(c, offNow, ss.Parts)
				errc <- nil
			}()
		}
		off += int64(p.Size)
	}
	for _, errc := range errcs {
		if err := <-errc; err != nil {
			return err
		}
	}
	return nil
}

type sliceWriter struct {
	dst []byte
}

func (sw *sliceWriter) Write(p []byte) (n int, err error) {
	n = copy(sw.dst, p)
	sw.dst = sw.dst[n:]
	return n, nil
}

var eofReader io.ReadCloser = ioutil.NopCloser(strings.NewReader(""))

func (fr *FileReader) getSuperset(br *blobref.BlobRef) (*Superset, error) {
	brStr := br.String()

	// Check cache first.
	fr.ssmmu.Lock()
	ss, ok := fr.ssm[brStr]
	fr.ssmmu.Unlock()
	if ok {
		return ss, nil
	}

	ssi, err := fr.sfg.Do(brStr, func() (interface{}, error) {
		rsc, _, err := fr.fetcher.Fetch(br)
		if err != nil {
			return nil, fmt.Errorf("schema/filereader: fetching file schema blob: %v", err)
		}
		defer rsc.Close()
		return ParseSuperset(rsc)
	})
	if err != nil {
		return nil, err
	}
	return ssi.(*Superset), nil
}

// readerForOffset returns a ReadCloser that reads some number of bytes and then EOF
// from the provided offset.  Seeing EOF doesn't mean the end of the whole file; just the
// chunk at that offset.  The caller must close the ReadCloser when done reading.
func (fr *FileReader) readerForOffset(off int64) (io.ReadCloser, error) {
	if off < 0 {
		panic("negative offset")
	}
	if off >= fr.size {
		return eofReader, nil
	}
	offRemain := off
	parts := fr.ss.Parts
	for len(parts) > 0 && parts[0].Size <= uint64(offRemain) {
		offRemain -= int64(parts[0].Size)
		parts = parts[1:]
	}
	if len(parts) == 0 {
		return eofReader, nil
	}
	p0 := parts[0]
	var rsc blobref.ReadSeekCloser
	var err error
	switch {
	case p0.BlobRef != nil && p0.BytesRef != nil:
		return nil, fmt.Errorf("part illegally contained both a blobRef and bytesRef")
	case p0.BlobRef == nil && p0.BytesRef == nil:
		return &nZeros{int(p0.Size - uint64(offRemain))}, nil
	case p0.BlobRef != nil:
		rsc, _, err = fr.fetcher.Fetch(p0.BlobRef)
	case p0.BytesRef != nil:
		var ss *Superset
		ss, err = fr.getSuperset(p0.BytesRef)
		if err != nil {
			return nil, err
		}
		rsc, err = ss.NewFileReader(fr.fetcher)
	}
	if err != nil {
		return nil, err
	}
	offRemain += int64(p0.Offset)
	if offRemain > 0 {
		newPos, err := rsc.Seek(offRemain, os.SEEK_SET)
		if err != nil {
			return nil, err
		}
		if newPos != offRemain {
			panic("Seek didn't work")
		}
	}
	return struct {
		io.Reader
		io.Closer
	}{
		io.LimitReader(rsc, int64(p0.Size)),
		rsc,
	}, nil
}

// nZeros is a ReadCloser that reads remain zeros before EOF.
type nZeros struct {
	remain int
}

func (z *nZeros) Read(p []byte) (n int, err error) {
	for len(p) > 0 && z.remain > 0 {
		p[0] = 0
		n++
		z.remain--
	}
	if n == 0 && z.remain == 0 {
		err = io.EOF
	}
	return
}

func (*nZeros) Close() error { return nil }
<commit_msg>schema: actually populate the superset cache map. whoops.<commit_after>/*
Copyright 2011 Google 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 schema

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"strings"
	"sync"

	"camlistore.org/pkg/blobref"
	"camlistore.org/pkg/singleflight"
)

var _ = log.Printf

const closedIndex = -1

var errClosed = errors.New("filereader is closed")

// A FileReader reads the bytes of "file" and "bytes" schema blobrefs.
type FileReader struct {
	*io.SectionReader

	ssmmu sync.Mutex           // guards ssm
	ssm   map[string]*Superset // blobref -> superset

	sfg singleflight.Group // for loading blobrefs for ssm

	fetcher blobref.SeekFetcher
	ss      *Superset

	size int64 // total number of bytes
}

// NewFileReader returns a new FileReader reading the contents of fileBlobRef,
// fetching blobs from fetcher.  The fileBlobRef must be of a "bytes" or "file"
// schema blob.
//
// The caller should call Close on the FileReader when done reading.
func NewFileReader(fetcher blobref.SeekFetcher, fileBlobRef *blobref.BlobRef) (*FileReader, error) {
	// TODO(bradfitz): make this take a blobref.FetcherAt instead?
	// TODO(bradfitz): rename this into bytes reader? but for now it's still
	//                 named FileReader, but can also read a "bytes" schema.
	if fileBlobRef == nil {
		return nil, errors.New("schema/filereader: NewFileReader blobref was nil")
	}
	rsc, _, err := fetcher.Fetch(fileBlobRef)
	if err != nil {
		return nil, fmt.Errorf("schema/filereader: fetching file schema blob: %v", err)
	}
	defer rsc.Close()
	ss, err := ParseSuperset(rsc)
	if err != nil {
		return nil, fmt.Errorf("schema/filereader: decoding file schema blob: %v", err)
	}
	if ss.Type != "file" && ss.Type != "bytes" {
		return nil, fmt.Errorf("schema/filereader: expected \"file\" or \"bytes\" schema blob, got %q", ss.Type)
	}
	fr, err := ss.NewFileReader(fetcher)
	if err != nil {
		return nil, fmt.Errorf("schema/filereader: creating FileReader for %s: %v", fileBlobRef, err)
	}
	return fr, nil
}

// NewFileReader returns a new FileReader, reading bytes and blobs
// from the provided fetcher.
//
// NewFileReader does no fetch operation on the fetcher itself.  The
// fetcher is only used in subsequent read operations.
//
// An error is only returned if the type of the Superset is not either
// "file" or "bytes".
func (ss *Superset) NewFileReader(fetcher blobref.SeekFetcher) (*FileReader, error) {
	if ss.Type != "file" && ss.Type != "bytes" {
		return nil, fmt.Errorf("schema/filereader: Superset not of type \"file\" or \"bytes\"")
	}
	size := int64(ss.SumPartsSize())
	fr := &FileReader{
		fetcher: fetcher,
		ss:      ss,
		size:    size,
		ssm:     make(map[string]*Superset),
	}
	fr.SectionReader = io.NewSectionReader(fr, 0, size)
	return fr, nil
}

// LoadAllChunks causes all chunks of the file to be loaded as quickly
// as possible.  The contents are immediately discarded, so it is
// assumed that the fetcher is a caching fetcher.
func (fr *FileReader) LoadAllChunks() {
	offsetc := make(chan int64, 16)
	go func() {
		for off := range offsetc {
			go func(off int64) {
				rc, err := fr.readerForOffset(off)
				if err == nil {
					rc.Close()
				}
			}(off)
		}
	}()
	go fr.GetChunkOffsets(offsetc)
}

// FileSchema returns the reader's schema superset. Don't mutate it.
func (fr *FileReader) FileSchema() *Superset {
	return fr.ss
}

func (fr *FileReader) Close() error {
	// TODO: close cached blobs?
	return nil
}

var _ interface {
	io.ReaderAt
	io.Reader
	io.Closer
	Size() int64
} = (*FileReader)(nil)

func (fr *FileReader) ReadAt(p []byte, offset int64) (n int, err error) {
	if offset < 0 {
		return 0, errors.New("schema/filereader: negative offset")
	}
	if offset >= fr.Size() {
		return 0, io.EOF
	}
	want := len(p)
	for len(p) > 0 && err == nil {
		var rc io.ReadCloser
		rc, err = fr.readerForOffset(offset)
		if err != nil {
			return
		}
		var n1 int64 // never bigger than an int
		n1, err = io.CopyN(&sliceWriter{p}, rc, int64(len(p)))
		rc.Close()
		if err == io.EOF {
			err = nil
		}
		if n1 == 0 {
			break
		}
		p = p[n1:]
		offset += int64(n1)
		n += int(n1)
	}
	if n < want && err == nil {
		err = io.ErrUnexpectedEOF
	}
	return n, err
}

// GetChunkOffsets sends c each of the file's chunk offsets.
// The offsets are not necessarily sent in order, and all ranges of the file
// are not necessarily represented if the file contains zero holes.
// The channel c is closed before the function returns, regardless of error.
func (fr *FileReader) GetChunkOffsets(c chan<- int64) error {
	defer close(c)
	return fr.sendPartsChunks(c, 0, fr.ss.Parts)
}

func (fr *FileReader) sendPartsChunks(c chan<- int64, off int64, parts []*BytesPart) error {
	var errcs []chan error
	for _, p := range parts {
		switch {
		case p.BlobRef != nil && p.BytesRef != nil:
			return fmt.Errorf("part illegally contained both a blobRef and bytesRef")
		case p.BlobRef == nil && p.BytesRef == nil:
			// Don't send
			break
		case p.BlobRef != nil:
			c <- off
		case p.BytesRef != nil:
			errc := make(chan error, 1)
			errcs = append(errcs, errc)
			br := p.BytesRef
			offNow := off
			go func() {
				ss, err := fr.getSuperset(br)
				if err != nil {
					errc <- err
					return
				}
				fr.sendPartsChunks(c, offNow, ss.Parts)
				errc <- nil
			}()
		}
		off += int64(p.Size)
	}
	for _, errc := range errcs {
		if err := <-errc; err != nil {
			return err
		}
	}
	return nil
}

type sliceWriter struct {
	dst []byte
}

func (sw *sliceWriter) Write(p []byte) (n int, err error) {
	n = copy(sw.dst, p)
	sw.dst = sw.dst[n:]
	return n, nil
}

var eofReader io.ReadCloser = ioutil.NopCloser(strings.NewReader(""))

func (fr *FileReader) getSuperset(br *blobref.BlobRef) (*Superset, error) {
	brStr := br.String()
	ssi, err := fr.sfg.Do(brStr, func() (interface{}, error) {
		fr.ssmmu.Lock()
		ss, ok := fr.ssm[brStr]
		fr.ssmmu.Unlock()
		if ok {
			return ss, nil
		}
		rsc, _, err := fr.fetcher.Fetch(br)
		if err != nil {
			return nil, fmt.Errorf("schema/filereader: fetching file schema blob: %v", err)
		}
		defer rsc.Close()
		ss, err = ParseSuperset(rsc)
		if err != nil {
			return nil, err
		}
		fr.ssmmu.Lock()
		defer fr.ssmmu.Unlock()
		fr.ssm[brStr] = ss
		return ss, nil
	})
	if err != nil {
		return nil, err
	}
	return ssi.(*Superset), nil
}

// readerForOffset returns a ReadCloser that reads some number of bytes and then EOF
// from the provided offset.  Seeing EOF doesn't mean the end of the whole file; just the
// chunk at that offset.  The caller must close the ReadCloser when done reading.
func (fr *FileReader) readerForOffset(off int64) (io.ReadCloser, error) {
	if off < 0 {
		panic("negative offset")
	}
	if off >= fr.size {
		return eofReader, nil
	}
	offRemain := off
	parts := fr.ss.Parts
	for len(parts) > 0 && parts[0].Size <= uint64(offRemain) {
		offRemain -= int64(parts[0].Size)
		parts = parts[1:]
	}
	if len(parts) == 0 {
		return eofReader, nil
	}
	p0 := parts[0]
	var rsc blobref.ReadSeekCloser
	var err error
	switch {
	case p0.BlobRef != nil && p0.BytesRef != nil:
		return nil, fmt.Errorf("part illegally contained both a blobRef and bytesRef")
	case p0.BlobRef == nil && p0.BytesRef == nil:
		return &nZeros{int(p0.Size - uint64(offRemain))}, nil
	case p0.BlobRef != nil:
		rsc, _, err = fr.fetcher.Fetch(p0.BlobRef)
	case p0.BytesRef != nil:
		var ss *Superset
		ss, err = fr.getSuperset(p0.BytesRef)
		if err != nil {
			return nil, err
		}
		rsc, err = ss.NewFileReader(fr.fetcher)
	}
	if err != nil {
		return nil, err
	}
	offRemain += int64(p0.Offset)
	if offRemain > 0 {
		newPos, err := rsc.Seek(offRemain, os.SEEK_SET)
		if err != nil {
			return nil, err
		}
		if newPos != offRemain {
			panic("Seek didn't work")
		}
	}
	return struct {
		io.Reader
		io.Closer
	}{
		io.LimitReader(rsc, int64(p0.Size)),
		rsc,
	}, nil
}

// nZeros is a ReadCloser that reads remain zeros before EOF.
type nZeros struct {
	remain int
}

func (z *nZeros) Read(p []byte) (n int, err error) {
	for len(p) > 0 && z.remain > 0 {
		p[0] = 0
		n++
		z.remain--
	}
	if n == 0 && z.remain == 0 {
		err = io.EOF
	}
	return
}

func (*nZeros) Close() error { return nil }
<|endoftext|>


<commit_before>package system

import "syscall"

// fromStatT converts a syscall.Stat_t type to a system.Stat_t type
func fromStatT(s *syscall.Stat_t) (*StatT, error) {
	return &StatT{size: s.Size,
		mode: s.Mode,
		uid:  s.Uid,
		gid:  s.Gid,
		rdev: s.Rdev,
		mtim: s.Mtim}, nil
}

// FromStatT converts a syscall.Stat_t type to a system.Stat_t type
// This is exposed on Linux as pkg/archive/changes uses it.
func FromStatT(s *syscall.Stat_t) (*StatT, error) {
	return fromStatT(s)
}
<commit_msg>Need this fix to allow builds on mipsel<commit_after>package system

import "syscall"

// fromStatT converts a syscall.Stat_t type to a system.Stat_t type
func fromStatT(s *syscall.Stat_t) (*StatT, error) {
	return &StatT{size: s.Size,
		mode: s.Mode,
		uid:  s.Uid,
		gid:  s.Gid,
		rdev: uint64(s.Rdev),
		mtim: s.Mtim}, nil
}

// FromStatT converts a syscall.Stat_t type to a system.Stat_t type
// This is exposed on Linux as pkg/archive/changes uses it.
func FromStatT(s *syscall.Stat_t) (*StatT, error) {
	return fromStatT(s)
}
<|endoftext|>


<commit_before>// +build windows

package term

import (
	"fmt"
	"io"
	"os"
	"os/signal"

	"github.com/Azure/go-ansiterm/winterm"
	"github.com/Sirupsen/logrus"
	"github.com/docker/docker/pkg/term/windows"
)

// State holds the console mode for the terminal.
type State struct {
	mode uint32
}

// Winsize is used for window size.
type Winsize struct {
	Height uint16
	Width  uint16
	x      uint16
	y      uint16
}

// StdStreams returns the standard streams (stdin, stdout, stedrr).
func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
	switch {
	case os.Getenv("ConEmuANSI") == "ON":
		// The ConEmu shell emulates ANSI well by default.
		return os.Stdin, os.Stdout, os.Stderr
	case os.Getenv("MSYSTEM") != "":
		// MSYS (mingw) does not emulate ANSI well.
		return windows.ConsoleStreams()
	default:
		return windows.ConsoleStreams()
	}
}

// GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal.
func GetFdInfo(in interface{}) (uintptr, bool) {
	return windows.GetHandleInfo(in)
}

// GetWinsize returns the window size based on the specified file descriptor.
func GetWinsize(fd uintptr) (*Winsize, error) {

	info, err := winterm.GetConsoleScreenBufferInfo(fd)
	if err != nil {
		return nil, err
	}

	winsize := &Winsize{
		Width:  uint16(info.Window.Right - info.Window.Left + 1),
		Height: uint16(info.Window.Bottom - info.Window.Top + 1),
		x:      0,
		y:      0}

	// Note: GetWinsize is called frequently -- uncomment only for excessive details
	// logrus.Debugf("[windows] GetWinsize: Console(%v)", info.String())
	// logrus.Debugf("[windows] GetWinsize: Width(%v), Height(%v), x(%v), y(%v)", winsize.Width, winsize.Height, winsize.x, winsize.y)
	return winsize, nil
}

// SetWinsize tries to set the specified window size for the specified file descriptor.
func SetWinsize(fd uintptr, ws *Winsize) error {

	// Ensure the requested dimensions are no larger than the maximum window size
	info, err := winterm.GetConsoleScreenBufferInfo(fd)
	if err != nil {
		return err
	}

	if ws.Width == 0 || ws.Height == 0 || ws.Width > uint16(info.MaximumWindowSize.X) || ws.Height > uint16(info.MaximumWindowSize.Y) {
		return fmt.Errorf("Illegal window size: (%v,%v) -- Maximum allow: (%v,%v)",
			ws.Width, ws.Height, info.MaximumWindowSize.X, info.MaximumWindowSize.Y)
	}

	// Narrow the sizes to that used by Windows
	width := winterm.SHORT(ws.Width)
	height := winterm.SHORT(ws.Height)

	// Set the dimensions while ensuring they remain within the bounds of the backing console buffer
	// -- Shrinking will always succeed. Growing may push the edges past the buffer boundary. When that occurs,
	//    shift the upper left just enough to keep the new window within the buffer.
	rect := info.Window
	if width < rect.Right-rect.Left+1 {
		rect.Right = rect.Left + width - 1
	} else if width > rect.Right-rect.Left+1 {
		rect.Right = rect.Left + width - 1
		if rect.Right >= info.Size.X {
			rect.Left = info.Size.X - width
			rect.Right = info.Size.X - 1
		}
	}

	if height < rect.Bottom-rect.Top+1 {
		rect.Bottom = rect.Top + height - 1
	} else if height > rect.Bottom-rect.Top+1 {
		rect.Bottom = rect.Top + height - 1
		if rect.Bottom >= info.Size.Y {
			rect.Top = info.Size.Y - height
			rect.Bottom = info.Size.Y - 1
		}
	}
	logrus.Debugf("[windows] SetWinsize: Requested((%v,%v)) Actual(%v)", ws.Width, ws.Height, rect)

	return winterm.SetConsoleWindowInfo(fd, true, rect)
}

// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(fd uintptr) bool {
	return windows.IsConsole(fd)
}

// RestoreTerminal restores the terminal connected to the given file descriptor
// to a previous state.
func RestoreTerminal(fd uintptr, state *State) error {
	return winterm.SetConsoleMode(fd, state.mode)
}

// SaveState saves the state of the terminal connected to the given file descriptor.
func SaveState(fd uintptr) (*State, error) {
	mode, e := winterm.GetConsoleMode(fd)
	if e != nil {
		return nil, e
	}
	return &State{mode}, nil
}

// DisableEcho disables echo for the terminal connected to the given file descriptor.
// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
func DisableEcho(fd uintptr, state *State) error {
	mode := state.mode
	mode &^= winterm.ENABLE_ECHO_INPUT
	mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT

	err := winterm.SetConsoleMode(fd, mode)
	if err != nil {
		return err
	}

	// Register an interrupt handler to catch and restore prior state
	restoreAtInterrupt(fd, state)
	return nil
}

// SetRawTerminal puts the terminal connected to the given file descriptor into raw
// mode and returns the previous state.
func SetRawTerminal(fd uintptr) (*State, error) {
	state, err := MakeRaw(fd)
	if err != nil {
		return nil, err
	}

	// Register an interrupt handler to catch and restore prior state
	restoreAtInterrupt(fd, state)
	return state, err
}

// MakeRaw puts the terminal (Windows Console) connected to the given file descriptor into raw
// mode and returns the previous state of the terminal so that it can be restored.
func MakeRaw(fd uintptr) (*State, error) {
	state, err := SaveState(fd)
	if err != nil {
		return nil, err
	}

	// See
	// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
	// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
	mode := state.mode

	// Disable these modes
	mode &^= winterm.ENABLE_ECHO_INPUT
	mode &^= winterm.ENABLE_LINE_INPUT
	mode &^= winterm.ENABLE_MOUSE_INPUT
	mode &^= winterm.ENABLE_WINDOW_INPUT
	mode &^= winterm.ENABLE_PROCESSED_INPUT

	// Enable these modes
	mode |= winterm.ENABLE_EXTENDED_FLAGS
	mode |= winterm.ENABLE_INSERT_MODE
	mode |= winterm.ENABLE_QUICK_EDIT_MODE

	err = winterm.SetConsoleMode(fd, mode)
	if err != nil {
		return nil, err
	}
	return state, nil
}

func restoreAtInterrupt(fd uintptr, state *State) {
	sigchan := make(chan os.Signal, 1)
	signal.Notify(sigchan, os.Interrupt)

	go func() {
		_ = <-sigchan
		RestoreTerminal(fd, state)
		os.Exit(0)
	}()
}
<commit_msg>Windows: Native ANSI console support<commit_after>// +build windows

package term

import (
	"fmt"
	"io"
	"os"
	"os/signal"
	"syscall"

	"github.com/Azure/go-ansiterm/winterm"
	"github.com/Sirupsen/logrus"
	"github.com/docker/docker/pkg/system"
	"github.com/docker/docker/pkg/term/windows"
)

// State holds the console mode for the terminal.
type State struct {
	mode uint32
}

// Winsize is used for window size.
type Winsize struct {
	Height uint16
	Width  uint16
	x      uint16
	y      uint16
}

// StdStreams returns the standard streams (stdin, stdout, stedrr).
func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
	switch {
	case os.Getenv("ConEmuANSI") == "ON":
		// The ConEmu shell emulates ANSI well by default.
		return os.Stdin, os.Stdout, os.Stderr
	case os.Getenv("MSYSTEM") != "":
		// MSYS (mingw) does not emulate ANSI well.
		return windows.ConsoleStreams()
	default:
		if useNativeConsole() {
			return os.Stdin, os.Stdout, os.Stderr
		}
		return windows.ConsoleStreams()
	}
}

// useNativeConsole determines if the docker client should use the built-in
// console which supports ANSI emulation, or fall-back to the golang emulator
// (github.com/azure/go-ansiterm).
func useNativeConsole() bool {
	osv, err := system.GetOSVersion()
	if err != nil {
		return false
	}

	// Native console is not available major version 10
	if osv.MajorVersion < 10 {
		return false
	}

	// Must have a late pre-release TP4 build of Windows Server 2016/Windows 10 TH2 or later
	if osv.Build < 10578 {
		return false
	}

	// Environment variable override
	if e := os.Getenv("USE_NATIVE_CONSOLE"); e != "" {
		if e == "1" {
			return true
		}
		return false
	}

	// Get the handle to stdout
	stdOutHandle, err := syscall.GetStdHandle(syscall.STD_OUTPUT_HANDLE)
	if err != nil {
		return false
	}

	// Get the console mode from the consoles stdout handle
	var mode uint32
	if err := syscall.GetConsoleMode(stdOutHandle, &mode); err != nil {
		return false
	}

	// Legacy mode does not have native ANSI emulation.
	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx
	const enableVirtualTerminalProcessing = 0x0004
	if mode&enableVirtualTerminalProcessing == 0 {
		return false
	}

	// TODO Windows (Post TP4). The native emulator still has issues which
	// mean it shouldn't be enabled for everyone. Change this next line to true
	// to change the default to "enable if available". In the meantime, users
	// can still try it out by using USE_NATIVE_CONSOLE env variable.
	return false
}

// GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal.
func GetFdInfo(in interface{}) (uintptr, bool) {
	return windows.GetHandleInfo(in)
}

// GetWinsize returns the window size based on the specified file descriptor.
func GetWinsize(fd uintptr) (*Winsize, error) {

	info, err := winterm.GetConsoleScreenBufferInfo(fd)
	if err != nil {
		return nil, err
	}

	winsize := &Winsize{
		Width:  uint16(info.Window.Right - info.Window.Left + 1),
		Height: uint16(info.Window.Bottom - info.Window.Top + 1),
		x:      0,
		y:      0}

	// Note: GetWinsize is called frequently -- uncomment only for excessive details
	// logrus.Debugf("[windows] GetWinsize: Console(%v)", info.String())
	// logrus.Debugf("[windows] GetWinsize: Width(%v), Height(%v), x(%v), y(%v)", winsize.Width, winsize.Height, winsize.x, winsize.y)
	return winsize, nil
}

// SetWinsize tries to set the specified window size for the specified file descriptor.
func SetWinsize(fd uintptr, ws *Winsize) error {

	// Ensure the requested dimensions are no larger than the maximum window size
	info, err := winterm.GetConsoleScreenBufferInfo(fd)
	if err != nil {
		return err
	}

	if ws.Width == 0 || ws.Height == 0 || ws.Width > uint16(info.MaximumWindowSize.X) || ws.Height > uint16(info.MaximumWindowSize.Y) {
		return fmt.Errorf("Illegal window size: (%v,%v) -- Maximum allow: (%v,%v)",
			ws.Width, ws.Height, info.MaximumWindowSize.X, info.MaximumWindowSize.Y)
	}

	// Narrow the sizes to that used by Windows
	width := winterm.SHORT(ws.Width)
	height := winterm.SHORT(ws.Height)

	// Set the dimensions while ensuring they remain within the bounds of the backing console buffer
	// -- Shrinking will always succeed. Growing may push the edges past the buffer boundary. When that occurs,
	//    shift the upper left just enough to keep the new window within the buffer.
	rect := info.Window
	if width < rect.Right-rect.Left+1 {
		rect.Right = rect.Left + width - 1
	} else if width > rect.Right-rect.Left+1 {
		rect.Right = rect.Left + width - 1
		if rect.Right >= info.Size.X {
			rect.Left = info.Size.X - width
			rect.Right = info.Size.X - 1
		}
	}

	if height < rect.Bottom-rect.Top+1 {
		rect.Bottom = rect.Top + height - 1
	} else if height > rect.Bottom-rect.Top+1 {
		rect.Bottom = rect.Top + height - 1
		if rect.Bottom >= info.Size.Y {
			rect.Top = info.Size.Y - height
			rect.Bottom = info.Size.Y - 1
		}
	}
	logrus.Debugf("[windows] SetWinsize: Requested((%v,%v)) Actual(%v)", ws.Width, ws.Height, rect)

	return winterm.SetConsoleWindowInfo(fd, true, rect)
}

// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(fd uintptr) bool {
	return windows.IsConsole(fd)
}

// RestoreTerminal restores the terminal connected to the given file descriptor
// to a previous state.
func RestoreTerminal(fd uintptr, state *State) error {
	return winterm.SetConsoleMode(fd, state.mode)
}

// SaveState saves the state of the terminal connected to the given file descriptor.
func SaveState(fd uintptr) (*State, error) {
	mode, e := winterm.GetConsoleMode(fd)
	if e != nil {
		return nil, e
	}
	return &State{mode}, nil
}

// DisableEcho disables echo for the terminal connected to the given file descriptor.
// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
func DisableEcho(fd uintptr, state *State) error {
	mode := state.mode
	mode &^= winterm.ENABLE_ECHO_INPUT
	mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT

	err := winterm.SetConsoleMode(fd, mode)
	if err != nil {
		return err
	}

	// Register an interrupt handler to catch and restore prior state
	restoreAtInterrupt(fd, state)
	return nil
}

// SetRawTerminal puts the terminal connected to the given file descriptor into raw
// mode and returns the previous state.
func SetRawTerminal(fd uintptr) (*State, error) {
	state, err := MakeRaw(fd)
	if err != nil {
		return nil, err
	}

	// Register an interrupt handler to catch and restore prior state
	restoreAtInterrupt(fd, state)
	return state, err
}

// MakeRaw puts the terminal (Windows Console) connected to the given file descriptor into raw
// mode and returns the previous state of the terminal so that it can be restored.
func MakeRaw(fd uintptr) (*State, error) {
	state, err := SaveState(fd)
	if err != nil {
		return nil, err
	}

	// See
	// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
	// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
	mode := state.mode

	// Disable these modes
	mode &^= winterm.ENABLE_ECHO_INPUT
	mode &^= winterm.ENABLE_LINE_INPUT
	mode &^= winterm.ENABLE_MOUSE_INPUT
	mode &^= winterm.ENABLE_WINDOW_INPUT
	mode &^= winterm.ENABLE_PROCESSED_INPUT

	// Enable these modes
	mode |= winterm.ENABLE_EXTENDED_FLAGS
	mode |= winterm.ENABLE_INSERT_MODE
	mode |= winterm.ENABLE_QUICK_EDIT_MODE

	err = winterm.SetConsoleMode(fd, mode)
	if err != nil {
		return nil, err
	}
	return state, nil
}

func restoreAtInterrupt(fd uintptr, state *State) {
	sigchan := make(chan os.Signal, 1)
	signal.Notify(sigchan, os.Interrupt)

	go func() {
		_ = <-sigchan
		RestoreTerminal(fd, state)
		os.Exit(0)
	}()
}
<|endoftext|>


<commit_before>// +build windows

package term

import (
	"fmt"
	"io"
	"os"
	"os/signal"
	"syscall"

	"github.com/Azure/go-ansiterm/winterm"
	"github.com/Sirupsen/logrus"
	"github.com/docker/docker/pkg/system"
	"github.com/docker/docker/pkg/term/windows"
)

// State holds the console mode for the terminal.
type State struct {
	mode uint32
}

// Winsize is used for window size.
type Winsize struct {
	Height uint16
	Width  uint16
	x      uint16
	y      uint16
}

// StdStreams returns the standard streams (stdin, stdout, stedrr).
func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
	switch {
	case os.Getenv("ConEmuANSI") == "ON":
		// The ConEmu shell emulates ANSI well by default.
		return os.Stdin, os.Stdout, os.Stderr
	case os.Getenv("MSYSTEM") != "":
		// MSYS (mingw) does not emulate ANSI well.
		return windows.ConsoleStreams()
	default:
		if useNativeConsole() {
			return os.Stdin, os.Stdout, os.Stderr
		}
		return windows.ConsoleStreams()
	}
}

// useNativeConsole determines if the docker client should use the built-in
// console which supports ANSI emulation, or fall-back to the golang emulator
// (github.com/azure/go-ansiterm).
func useNativeConsole() bool {
	osv, err := system.GetOSVersion()
	if err != nil {
		return false
	}

	// Native console is not available major version 10
	if osv.MajorVersion < 10 {
		return false
	}

	// Must have a late pre-release TP4 build of Windows Server 2016/Windows 10 TH2 or later
	if osv.Build < 10578 {
		return false
	}

	// Environment variable override
	if e := os.Getenv("USE_NATIVE_CONSOLE"); e != "" {
		if e == "1" {
			return true
		}
		return false
	}

	// Get the handle to stdout
	stdOutHandle, err := syscall.GetStdHandle(syscall.STD_OUTPUT_HANDLE)
	if err != nil {
		return false
	}

	// Get the console mode from the consoles stdout handle
	var mode uint32
	if err := syscall.GetConsoleMode(stdOutHandle, &mode); err != nil {
		return false
	}

	// Legacy mode does not have native ANSI emulation.
	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx
	const enableVirtualTerminalProcessing = 0x0004
	if mode&enableVirtualTerminalProcessing == 0 {
		return false
	}

	// TODO Windows (Post TP4). The native emulator still has issues which
	// mean it shouldn't be enabled for everyone. Change this next line to true
	// to change the default to "enable if available". In the meantime, users
	// can still try it out by using USE_NATIVE_CONSOLE env variable.
	return false
}

// GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal.
func GetFdInfo(in interface{}) (uintptr, bool) {
	return windows.GetHandleInfo(in)
}

// GetWinsize returns the window size based on the specified file descriptor.
func GetWinsize(fd uintptr) (*Winsize, error) {

	info, err := winterm.GetConsoleScreenBufferInfo(fd)
	if err != nil {
		return nil, err
	}

	winsize := &Winsize{
		Width:  uint16(info.Window.Right - info.Window.Left + 1),
		Height: uint16(info.Window.Bottom - info.Window.Top + 1),
		x:      0,
		y:      0}

	// Note: GetWinsize is called frequently -- uncomment only for excessive details
	// logrus.Debugf("[windows] GetWinsize: Console(%v)", info.String())
	// logrus.Debugf("[windows] GetWinsize: Width(%v), Height(%v), x(%v), y(%v)", winsize.Width, winsize.Height, winsize.x, winsize.y)
	return winsize, nil
}

// SetWinsize tries to set the specified window size for the specified file descriptor.
func SetWinsize(fd uintptr, ws *Winsize) error {

	// Ensure the requested dimensions are no larger than the maximum window size
	info, err := winterm.GetConsoleScreenBufferInfo(fd)
	if err != nil {
		return err
	}

	if ws.Width == 0 || ws.Height == 0 || ws.Width > uint16(info.MaximumWindowSize.X) || ws.Height > uint16(info.MaximumWindowSize.Y) {
		return fmt.Errorf("Illegal window size: (%v,%v) -- Maximum allow: (%v,%v)",
			ws.Width, ws.Height, info.MaximumWindowSize.X, info.MaximumWindowSize.Y)
	}

	// Narrow the sizes to that used by Windows
	width := winterm.SHORT(ws.Width)
	height := winterm.SHORT(ws.Height)

	// Set the dimensions while ensuring they remain within the bounds of the backing console buffer
	// -- Shrinking will always succeed. Growing may push the edges past the buffer boundary. When that occurs,
	//    shift the upper left just enough to keep the new window within the buffer.
	rect := info.Window
	if width < rect.Right-rect.Left+1 {
		rect.Right = rect.Left + width - 1
	} else if width > rect.Right-rect.Left+1 {
		rect.Right = rect.Left + width - 1
		if rect.Right >= info.Size.X {
			rect.Left = info.Size.X - width
			rect.Right = info.Size.X - 1
		}
	}

	if height < rect.Bottom-rect.Top+1 {
		rect.Bottom = rect.Top + height - 1
	} else if height > rect.Bottom-rect.Top+1 {
		rect.Bottom = rect.Top + height - 1
		if rect.Bottom >= info.Size.Y {
			rect.Top = info.Size.Y - height
			rect.Bottom = info.Size.Y - 1
		}
	}
	logrus.Debugf("[windows] SetWinsize: Requested((%v,%v)) Actual(%v)", ws.Width, ws.Height, rect)

	return winterm.SetConsoleWindowInfo(fd, true, rect)
}

// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(fd uintptr) bool {
	return windows.IsConsole(fd)
}

// RestoreTerminal restores the terminal connected to the given file descriptor
// to a previous state.
func RestoreTerminal(fd uintptr, state *State) error {
	return winterm.SetConsoleMode(fd, state.mode)
}

// SaveState saves the state of the terminal connected to the given file descriptor.
func SaveState(fd uintptr) (*State, error) {
	mode, e := winterm.GetConsoleMode(fd)
	if e != nil {
		return nil, e
	}
	return &State{mode}, nil
}

// DisableEcho disables echo for the terminal connected to the given file descriptor.
// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
func DisableEcho(fd uintptr, state *State) error {
	mode := state.mode
	mode &^= winterm.ENABLE_ECHO_INPUT
	mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT

	err := winterm.SetConsoleMode(fd, mode)
	if err != nil {
		return err
	}

	// Register an interrupt handler to catch and restore prior state
	restoreAtInterrupt(fd, state)
	return nil
}

// SetRawTerminal puts the terminal connected to the given file descriptor into raw
// mode and returns the previous state.
func SetRawTerminal(fd uintptr) (*State, error) {
	state, err := MakeRaw(fd)
	if err != nil {
		return nil, err
	}

	// Register an interrupt handler to catch and restore prior state
	restoreAtInterrupt(fd, state)
	return state, err
}

// MakeRaw puts the terminal (Windows Console) connected to the given file descriptor into raw
// mode and returns the previous state of the terminal so that it can be restored.
func MakeRaw(fd uintptr) (*State, error) {
	state, err := SaveState(fd)
	if err != nil {
		return nil, err
	}

	// See
	// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
	// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
	mode := state.mode

	// Disable these modes
	mode &^= winterm.ENABLE_ECHO_INPUT
	mode &^= winterm.ENABLE_LINE_INPUT
	mode &^= winterm.ENABLE_MOUSE_INPUT
	mode &^= winterm.ENABLE_WINDOW_INPUT
	mode &^= winterm.ENABLE_PROCESSED_INPUT

	// Enable these modes
	mode |= winterm.ENABLE_EXTENDED_FLAGS
	mode |= winterm.ENABLE_INSERT_MODE
	mode |= winterm.ENABLE_QUICK_EDIT_MODE

	err = winterm.SetConsoleMode(fd, mode)
	if err != nil {
		return nil, err
	}
	return state, nil
}

func restoreAtInterrupt(fd uintptr, state *State) {
	sigchan := make(chan os.Signal, 1)
	signal.Notify(sigchan, os.Interrupt)

	go func() {
		_ = <-sigchan
		RestoreTerminal(fd, state)
		os.Exit(0)
	}()
}
<commit_msg>Windows: Remove dead code<commit_after>// +build windows

package term

import (
	"io"
	"os"
	"os/signal"
	"syscall"

	"github.com/Azure/go-ansiterm/winterm"
	"github.com/docker/docker/pkg/system"
	"github.com/docker/docker/pkg/term/windows"
)

// State holds the console mode for the terminal.
type State struct {
	mode uint32
}

// Winsize is used for window size.
type Winsize struct {
	Height uint16
	Width  uint16
	x      uint16
	y      uint16
}

// StdStreams returns the standard streams (stdin, stdout, stedrr).
func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
	switch {
	case os.Getenv("ConEmuANSI") == "ON":
		// The ConEmu shell emulates ANSI well by default.
		return os.Stdin, os.Stdout, os.Stderr
	case os.Getenv("MSYSTEM") != "":
		// MSYS (mingw) does not emulate ANSI well.
		return windows.ConsoleStreams()
	default:
		if useNativeConsole() {
			return os.Stdin, os.Stdout, os.Stderr
		}
		return windows.ConsoleStreams()
	}
}

// useNativeConsole determines if the docker client should use the built-in
// console which supports ANSI emulation, or fall-back to the golang emulator
// (github.com/azure/go-ansiterm).
func useNativeConsole() bool {
	osv, err := system.GetOSVersion()
	if err != nil {
		return false
	}

	// Native console is not available major version 10
	if osv.MajorVersion < 10 {
		return false
	}

	// Must have a late pre-release TP4 build of Windows Server 2016/Windows 10 TH2 or later
	if osv.Build < 10578 {
		return false
	}

	// Environment variable override
	if e := os.Getenv("USE_NATIVE_CONSOLE"); e != "" {
		if e == "1" {
			return true
		}
		return false
	}

	// Get the handle to stdout
	stdOutHandle, err := syscall.GetStdHandle(syscall.STD_OUTPUT_HANDLE)
	if err != nil {
		return false
	}

	// Get the console mode from the consoles stdout handle
	var mode uint32
	if err := syscall.GetConsoleMode(stdOutHandle, &mode); err != nil {
		return false
	}

	// Legacy mode does not have native ANSI emulation.
	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx
	const enableVirtualTerminalProcessing = 0x0004
	if mode&enableVirtualTerminalProcessing == 0 {
		return false
	}

	// TODO Windows (Post TP4). The native emulator still has issues which
	// mean it shouldn't be enabled for everyone. Change this next line to true
	// to change the default to "enable if available". In the meantime, users
	// can still try it out by using USE_NATIVE_CONSOLE env variable.
	return false
}

// GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal.
func GetFdInfo(in interface{}) (uintptr, bool) {
	return windows.GetHandleInfo(in)
}

// GetWinsize returns the window size based on the specified file descriptor.
func GetWinsize(fd uintptr) (*Winsize, error) {

	info, err := winterm.GetConsoleScreenBufferInfo(fd)
	if err != nil {
		return nil, err
	}

	winsize := &Winsize{
		Width:  uint16(info.Window.Right - info.Window.Left + 1),
		Height: uint16(info.Window.Bottom - info.Window.Top + 1),
		x:      0,
		y:      0}

	// Note: GetWinsize is called frequently -- uncomment only for excessive details
	// logrus.Debugf("[windows] GetWinsize: Console(%v)", info.String())
	// logrus.Debugf("[windows] GetWinsize: Width(%v), Height(%v), x(%v), y(%v)", winsize.Width, winsize.Height, winsize.x, winsize.y)
	return winsize, nil
}

// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(fd uintptr) bool {
	return windows.IsConsole(fd)
}

// RestoreTerminal restores the terminal connected to the given file descriptor
// to a previous state.
func RestoreTerminal(fd uintptr, state *State) error {
	return winterm.SetConsoleMode(fd, state.mode)
}

// SaveState saves the state of the terminal connected to the given file descriptor.
func SaveState(fd uintptr) (*State, error) {
	mode, e := winterm.GetConsoleMode(fd)
	if e != nil {
		return nil, e
	}
	return &State{mode}, nil
}

// DisableEcho disables echo for the terminal connected to the given file descriptor.
// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
func DisableEcho(fd uintptr, state *State) error {
	mode := state.mode
	mode &^= winterm.ENABLE_ECHO_INPUT
	mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT

	err := winterm.SetConsoleMode(fd, mode)
	if err != nil {
		return err
	}

	// Register an interrupt handler to catch and restore prior state
	restoreAtInterrupt(fd, state)
	return nil
}

// SetRawTerminal puts the terminal connected to the given file descriptor into raw
// mode and returns the previous state.
func SetRawTerminal(fd uintptr) (*State, error) {
	state, err := MakeRaw(fd)
	if err != nil {
		return nil, err
	}

	// Register an interrupt handler to catch and restore prior state
	restoreAtInterrupt(fd, state)
	return state, err
}

// MakeRaw puts the terminal (Windows Console) connected to the given file descriptor into raw
// mode and returns the previous state of the terminal so that it can be restored.
func MakeRaw(fd uintptr) (*State, error) {
	state, err := SaveState(fd)
	if err != nil {
		return nil, err
	}

	// See
	// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
	// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
	mode := state.mode

	// Disable these modes
	mode &^= winterm.ENABLE_ECHO_INPUT
	mode &^= winterm.ENABLE_LINE_INPUT
	mode &^= winterm.ENABLE_MOUSE_INPUT
	mode &^= winterm.ENABLE_WINDOW_INPUT
	mode &^= winterm.ENABLE_PROCESSED_INPUT

	// Enable these modes
	mode |= winterm.ENABLE_EXTENDED_FLAGS
	mode |= winterm.ENABLE_INSERT_MODE
	mode |= winterm.ENABLE_QUICK_EDIT_MODE

	err = winterm.SetConsoleMode(fd, mode)
	if err != nil {
		return nil, err
	}
	return state, nil
}

func restoreAtInterrupt(fd uintptr, state *State) {
	sigchan := make(chan os.Signal, 1)
	signal.Notify(sigchan, os.Interrupt)

	go func() {
		_ = <-sigchan
		RestoreTerminal(fd, state)
		os.Exit(0)
	}()
}
<|endoftext|>


<commit_before>package tun

import (
	"context"
	"errors"
	"fmt"
	"net"
	"os"
	"runtime"
	"unsafe"

	"golang.org/x/net/ipv4"
	"golang.org/x/net/ipv6"
	"golang.org/x/sys/unix"

	"github.com/telepresenceio/telepresence/v2/pkg/tun/buffer"
)

const sysProtoControl = 2
const uTunOptIfName = 2
const uTunControlName = "com.apple.net.utun_control"

type Device struct {
	*os.File
	name string
}

func openTun() (*Device, error) {
	fd, err := unix.Socket(unix.AF_SYSTEM, unix.SOCK_DGRAM, sysProtoControl)
	if err != nil {
		return nil, fmt.Errorf("failed to open DGRAM socket: %w", err)
	}
	unix.CloseOnExec(fd)
	defer func() {
		if err != nil {
			_ = unix.Close(fd)
		}
	}()

	info := &unix.CtlInfo{}
	copy(info.Name[:], uTunControlName)
	if err = unix.IoctlCtlInfo(fd, info); err != nil {
		return nil, fmt.Errorf("failed to getBuffer IOCTL info for %s: %w", uTunControlName, err)
	}

	if err = unix.Connect(fd, &unix.SockaddrCtl{ID: info.Id, Unit: 0}); err != nil {
		return nil, err
	}

	if err = unix.SetNonblock(fd, true); err != nil {
		return nil, err
	}

	name, err := unix.GetsockoptString(fd, sysProtoControl, uTunOptIfName)
	if err != nil {
		return nil, err
	}
	return &Device{
		File: os.NewFile(uintptr(fd), ""),
		name: name,
	}, nil
}

func (t *Device) addSubnet(_ context.Context, subnet *net.IPNet) error {
	to := make(net.IP, len(subnet.IP))
	copy(to, subnet.IP)
	to[len(to)-1] = 1
	if err := t.setAddr(subnet, to); err != nil {
		return err
	}
	return withRouteSocket(func(s int) error {
		return t.routeAdd(s, 1, subnet, to)
	})
}

func (t *Device) removeSubnet(_ context.Context, subnet *net.IPNet) error {
	to := make(net.IP, len(subnet.IP))
	copy(to, subnet.IP)
	to[len(to)-1] = 1
	if err := t.removeAddr(subnet, to); err != nil {
		return err
	}
	return withRouteSocket(func(s int) error {
		return t.routeClear(s, 1, subnet, to)
	})
}

func (t *Device) setMTU(mtu int) error {
	return withSocket(unix.AF_INET, func(fd int) error {
		var ifr unix.IfreqMTU
		copy(ifr.Name[:], t.name)
		ifr.MTU = int32(mtu)
		err := unix.IoctlSetIfreqMTU(fd, &ifr)
		if err != nil {
			err = fmt.Errorf("set MTU on %s failed: %w", t.name, err)
		}
		return err
	})
}

func (t *Device) readPacket(into *buffer.Data) (int, error) {
	n, err := t.File.Read(into.Raw())
	if n >= buffer.PrefixLen {
		n -= buffer.PrefixLen
	}
	return n, err
}

func (t *Device) writePacket(from *buffer.Data) (int, error) {
	raw := from.Raw()
	if len(raw) <= buffer.PrefixLen {
		return 0, unix.EIO
	}

	ipVer := raw[buffer.PrefixLen] >> 4
	switch ipVer {
	case ipv4.Version:
		raw[3] = unix.AF_INET
	case ipv6.Version:
		raw[3] = unix.AF_INET6
	default:
		return 0, errors.New("unable to determine IP version from packet")
	}
	n, err := t.File.Write(raw)
	return n - buffer.PrefixLen, err
}

// Address structure for the SIOCAIFADDR ioctlHandle request
//
// See https://www.unix.com/man-page/osx/4/netintro/
type addrIfReq struct {
	name [unix.IFNAMSIZ]byte
	addr unix.RawSockaddrInet4
	dest unix.RawSockaddrInet4
	mask unix.RawSockaddrInet4
}

// Address structure for the SIOCAIFADDR_IN6 ioctlHandle request
//
// See https://www.unix.com/man-page/osx/4/netintro/
type addrIfReq6 struct {
	name           [unix.IFNAMSIZ]byte
	addr           unix.RawSockaddrInet6
	dest           unix.RawSockaddrInet6
	mask           unix.RawSockaddrInet6
	flags          int32 // nolint:structcheck
	expire         int64 // nolint:structcheck
	preferred      int64 // nolint:structcheck
	validLifeTime  uint32
	prefixLifeTime uint32
}

// SIOCAIFADDR_IN6 is the same ioctlHandle identifier as unix.SIOCAIFADDR adjusted with size of addrIfReq6
const SIOCAIFADDR_IN6 = (unix.SIOCAIFADDR & 0xe000ffff) | (uint(unsafe.Sizeof(addrIfReq6{})) << 16)
const ND6_INFINITE_LIFETIME = 0xffffffff

// SIOCDIFADDR_IN6 is the same ioctlHandle identifier as unix.SIOCDIFADDR adjusted with size of addrIfReq6
const SIOCDIFADDR_IN6 = (unix.SIOCDIFADDR & 0xe000ffff) | (uint(unsafe.Sizeof(addrIfReq6{})) << 16)

func addrToIp4(subnet *net.IPNet, to net.IP) (*net.IPNet, net.IP, bool) {
	if to4 := to.To4(); to4 != nil {
		if dest4 := subnet.IP.To4(); dest4 != nil {
			if _, bits := subnet.Mask.Size(); bits == 32 {
				return &net.IPNet{IP: dest4, Mask: subnet.Mask}, to4, true
			}
		}
	}
	return nil, nil, false
}

func (t *Device) setAddr(subnet *net.IPNet, to net.IP) error {
	if sub4, to4, ok := addrToIp4(subnet, to); ok {
		return withSocket(unix.AF_INET, func(fd int) error {
			ifreq := &addrIfReq{
				addr: unix.RawSockaddrInet4{Len: 16, Family: unix.AF_INET},
				dest: unix.RawSockaddrInet4{Len: 16, Family: unix.AF_INET},
				mask: unix.RawSockaddrInet4{Len: 16, Family: unix.AF_INET},
			}
			copy(ifreq.name[:], t.name)
			copy(ifreq.addr.Addr[:], sub4.IP)
			copy(ifreq.mask.Addr[:], sub4.Mask)
			copy(ifreq.dest.Addr[:], to4)
			err := ioctl(fd, unix.SIOCAIFADDR, unsafe.Pointer(ifreq))
			runtime.KeepAlive(ifreq)
			return err
		})
	} else {
		return withSocket(unix.AF_INET6, func(fd int) error {
			ifreq := &addrIfReq6{
				addr:           unix.RawSockaddrInet6{Len: 28, Family: unix.AF_INET6},
				dest:           unix.RawSockaddrInet6{Len: 28, Family: unix.AF_INET6},
				mask:           unix.RawSockaddrInet6{Len: 28, Family: unix.AF_INET6},
				validLifeTime:  ND6_INFINITE_LIFETIME,
				prefixLifeTime: ND6_INFINITE_LIFETIME,
			}
			copy(ifreq.name[:], t.name)
			copy(ifreq.addr.Addr[:], subnet.IP.To16())
			copy(ifreq.mask.Addr[:], subnet.Mask)
			copy(ifreq.dest.Addr[:], to.To16())
			err := ioctl(fd, SIOCAIFADDR_IN6, unsafe.Pointer(ifreq))
			runtime.KeepAlive(ifreq)
			return err
		})
	}
}

func (t *Device) removeAddr(subnet *net.IPNet, to net.IP) error {
	if sub4, to4, ok := addrToIp4(subnet, to); ok {
		return withSocket(unix.AF_INET, func(fd int) error {
			ifreq := &addrIfReq{
				addr: unix.RawSockaddrInet4{Len: 16, Family: unix.AF_INET},
				dest: unix.RawSockaddrInet4{Len: 16, Family: unix.AF_INET},
				mask: unix.RawSockaddrInet4{Len: 16, Family: unix.AF_INET},
			}
			copy(ifreq.name[:], t.name)
			copy(ifreq.addr.Addr[:], sub4.IP)
			copy(ifreq.mask.Addr[:], sub4.Mask)
			copy(ifreq.dest.Addr[:], to4)
			err := ioctl(fd, unix.SIOCDIFADDR, unsafe.Pointer(ifreq))
			runtime.KeepAlive(ifreq)
			return err
		})
	} else {
		return withSocket(unix.AF_INET6, func(fd int) error {
			ifreq := &addrIfReq6{
				addr:           unix.RawSockaddrInet6{Len: 28, Family: unix.AF_INET6},
				dest:           unix.RawSockaddrInet6{Len: 28, Family: unix.AF_INET6},
				mask:           unix.RawSockaddrInet6{Len: 28, Family: unix.AF_INET6},
				validLifeTime:  ND6_INFINITE_LIFETIME,
				prefixLifeTime: ND6_INFINITE_LIFETIME,
			}
			copy(ifreq.name[:], t.name)
			copy(ifreq.addr.Addr[:], subnet.IP.To16())
			copy(ifreq.mask.Addr[:], subnet.Mask)
			copy(ifreq.dest.Addr[:], to.To16())
			err := ioctl(fd, SIOCDIFADDR_IN6, unsafe.Pointer(ifreq))
			runtime.KeepAlive(ifreq)
			return err
		})
	}
}
<commit_msg>Fix lint error on macos<commit_after>package tun

import (
	"context"
	"errors"
	"fmt"
	"net"
	"os"
	"runtime"
	"unsafe"

	"golang.org/x/net/ipv4"
	"golang.org/x/net/ipv6"
	"golang.org/x/sys/unix"

	"github.com/telepresenceio/telepresence/v2/pkg/tun/buffer"
)

const sysProtoControl = 2
const uTunOptIfName = 2
const uTunControlName = "com.apple.net.utun_control"

type Device struct {
	*os.File
	name string
}

func openTun() (*Device, error) {
	fd, err := unix.Socket(unix.AF_SYSTEM, unix.SOCK_DGRAM, sysProtoControl)
	if err != nil {
		return nil, fmt.Errorf("failed to open DGRAM socket: %w", err)
	}
	unix.CloseOnExec(fd)
	defer func() {
		if err != nil {
			_ = unix.Close(fd)
		}
	}()

	info := &unix.CtlInfo{}
	copy(info.Name[:], uTunControlName)
	if err = unix.IoctlCtlInfo(fd, info); err != nil {
		return nil, fmt.Errorf("failed to getBuffer IOCTL info for %s: %w", uTunControlName, err)
	}

	if err = unix.Connect(fd, &unix.SockaddrCtl{ID: info.Id, Unit: 0}); err != nil {
		return nil, err
	}

	if err = unix.SetNonblock(fd, true); err != nil {
		return nil, err
	}

	name, err := unix.GetsockoptString(fd, sysProtoControl, uTunOptIfName)
	if err != nil {
		return nil, err
	}
	return &Device{
		File: os.NewFile(uintptr(fd), ""),
		name: name,
	}, nil
}

func (t *Device) addSubnet(_ context.Context, subnet *net.IPNet) error {
	to := make(net.IP, len(subnet.IP))
	copy(to, subnet.IP)
	to[len(to)-1] = 1
	if err := t.setAddr(subnet, to); err != nil {
		return err
	}
	return withRouteSocket(func(s int) error {
		return t.routeAdd(s, 1, subnet, to)
	})
}

func (t *Device) removeSubnet(_ context.Context, subnet *net.IPNet) error {
	to := make(net.IP, len(subnet.IP))
	copy(to, subnet.IP)
	to[len(to)-1] = 1
	if err := t.removeAddr(subnet, to); err != nil {
		return err
	}
	return withRouteSocket(func(s int) error {
		return t.routeClear(s, 1, subnet, to)
	})
}

func (t *Device) setMTU(mtu int) error {
	return withSocket(unix.AF_INET, func(fd int) error {
		var ifr unix.IfreqMTU
		copy(ifr.Name[:], t.name)
		ifr.MTU = int32(mtu)
		err := unix.IoctlSetIfreqMTU(fd, &ifr)
		if err != nil {
			err = fmt.Errorf("set MTU on %s failed: %w", t.name, err)
		}
		return err
	})
}

func (t *Device) readPacket(into *buffer.Data) (int, error) {
	n, err := t.File.Read(into.Raw())
	if n >= buffer.PrefixLen {
		n -= buffer.PrefixLen
	}
	return n, err
}

func (t *Device) writePacket(from *buffer.Data) (int, error) {
	raw := from.Raw()
	if len(raw) <= buffer.PrefixLen {
		return 0, unix.EIO
	}

	ipVer := raw[buffer.PrefixLen] >> 4
	switch ipVer {
	case ipv4.Version:
		raw[3] = unix.AF_INET
	case ipv6.Version:
		raw[3] = unix.AF_INET6
	default:
		return 0, errors.New("unable to determine IP version from packet")
	}
	n, err := t.File.Write(raw)
	return n - buffer.PrefixLen, err
}

// Address structure for the SIOCAIFADDR ioctlHandle request
//
// See https://www.unix.com/man-page/osx/4/netintro/
type addrIfReq struct {
	name [unix.IFNAMSIZ]byte
	addr unix.RawSockaddrInet4
	dest unix.RawSockaddrInet4
	mask unix.RawSockaddrInet4
}

// Address structure for the SIOCAIFADDR_IN6 ioctlHandle request
//
// See https://www.unix.com/man-page/osx/4/netintro/
type addrIfReq6 struct {
	name           [unix.IFNAMSIZ]byte
	addr           unix.RawSockaddrInet6
	dest           unix.RawSockaddrInet6
	mask           unix.RawSockaddrInet6
	flags          int32 //nolint:structcheck
	expire         int64 //nolint:structcheck
	preferred      int64 //nolint:structcheck
	validLifeTime  uint32
	prefixLifeTime uint32
}

// SIOCAIFADDR_IN6 is the same ioctlHandle identifier as unix.SIOCAIFADDR adjusted with size of addrIfReq6
const SIOCAIFADDR_IN6 = (unix.SIOCAIFADDR & 0xe000ffff) | (uint(unsafe.Sizeof(addrIfReq6{})) << 16)
const ND6_INFINITE_LIFETIME = 0xffffffff

// SIOCDIFADDR_IN6 is the same ioctlHandle identifier as unix.SIOCDIFADDR adjusted with size of addrIfReq6
const SIOCDIFADDR_IN6 = (unix.SIOCDIFADDR & 0xe000ffff) | (uint(unsafe.Sizeof(addrIfReq6{})) << 16)

func addrToIp4(subnet *net.IPNet, to net.IP) (*net.IPNet, net.IP, bool) {
	if to4 := to.To4(); to4 != nil {
		if dest4 := subnet.IP.To4(); dest4 != nil {
			if _, bits := subnet.Mask.Size(); bits == 32 {
				return &net.IPNet{IP: dest4, Mask: subnet.Mask}, to4, true
			}
		}
	}
	return nil, nil, false
}

func (t *Device) setAddr(subnet *net.IPNet, to net.IP) error {
	if sub4, to4, ok := addrToIp4(subnet, to); ok {
		return withSocket(unix.AF_INET, func(fd int) error {
			ifreq := &addrIfReq{
				addr: unix.RawSockaddrInet4{Len: 16, Family: unix.AF_INET},
				dest: unix.RawSockaddrInet4{Len: 16, Family: unix.AF_INET},
				mask: unix.RawSockaddrInet4{Len: 16, Family: unix.AF_INET},
			}
			copy(ifreq.name[:], t.name)
			copy(ifreq.addr.Addr[:], sub4.IP)
			copy(ifreq.mask.Addr[:], sub4.Mask)
			copy(ifreq.dest.Addr[:], to4)
			err := ioctl(fd, unix.SIOCAIFADDR, unsafe.Pointer(ifreq))
			runtime.KeepAlive(ifreq)
			return err
		})
	} else {
		return withSocket(unix.AF_INET6, func(fd int) error {
			ifreq := &addrIfReq6{
				addr:           unix.RawSockaddrInet6{Len: 28, Family: unix.AF_INET6},
				dest:           unix.RawSockaddrInet6{Len: 28, Family: unix.AF_INET6},
				mask:           unix.RawSockaddrInet6{Len: 28, Family: unix.AF_INET6},
				validLifeTime:  ND6_INFINITE_LIFETIME,
				prefixLifeTime: ND6_INFINITE_LIFETIME,
			}
			copy(ifreq.name[:], t.name)
			copy(ifreq.addr.Addr[:], subnet.IP.To16())
			copy(ifreq.mask.Addr[:], subnet.Mask)
			copy(ifreq.dest.Addr[:], to.To16())
			err := ioctl(fd, SIOCAIFADDR_IN6, unsafe.Pointer(ifreq))
			runtime.KeepAlive(ifreq)
			return err
		})
	}
}

func (t *Device) removeAddr(subnet *net.IPNet, to net.IP) error {
	if sub4, to4, ok := addrToIp4(subnet, to); ok {
		return withSocket(unix.AF_INET, func(fd int) error {
			ifreq := &addrIfReq{
				addr: unix.RawSockaddrInet4{Len: 16, Family: unix.AF_INET},
				dest: unix.RawSockaddrInet4{Len: 16, Family: unix.AF_INET},
				mask: unix.RawSockaddrInet4{Len: 16, Family: unix.AF_INET},
			}
			copy(ifreq.name[:], t.name)
			copy(ifreq.addr.Addr[:], sub4.IP)
			copy(ifreq.mask.Addr[:], sub4.Mask)
			copy(ifreq.dest.Addr[:], to4)
			err := ioctl(fd, unix.SIOCDIFADDR, unsafe.Pointer(ifreq))
			runtime.KeepAlive(ifreq)
			return err
		})
	} else {
		return withSocket(unix.AF_INET6, func(fd int) error {
			ifreq := &addrIfReq6{
				addr:           unix.RawSockaddrInet6{Len: 28, Family: unix.AF_INET6},
				dest:           unix.RawSockaddrInet6{Len: 28, Family: unix.AF_INET6},
				mask:           unix.RawSockaddrInet6{Len: 28, Family: unix.AF_INET6},
				validLifeTime:  ND6_INFINITE_LIFETIME,
				prefixLifeTime: ND6_INFINITE_LIFETIME,
			}
			copy(ifreq.name[:], t.name)
			copy(ifreq.addr.Addr[:], subnet.IP.To16())
			copy(ifreq.mask.Addr[:], subnet.Mask)
			copy(ifreq.dest.Addr[:], to.To16())
			err := ioctl(fd, SIOCDIFADDR_IN6, unsafe.Pointer(ifreq))
			runtime.KeepAlive(ifreq)
			return err
		})
	}
}
<|endoftext|>


<commit_before>package util

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"errors"
	"fmt"
	"math"
	"math/big"
	"net"
	"strings"
	"time"

	"github.com/kennygrant/sanitize"
	certutil "k8s.io/client-go/util/cert"

	"github.com/sapcc/kubernikus/pkg/apis/kubernikus/v1"
)

const (
	//by default we generate certs with 2 year validity
	defaultCertValidity = 2 * time.Hour * 24 * 365
	//out CAs are valid for 10 years
	caValidity = 10 * time.Hour * 24 * 365
)

type Bundle struct {
	Certificate *x509.Certificate
	PrivateKey  *rsa.PrivateKey
}

type Certificates struct {
	Etcd struct {
		Clients struct {
			CA        Bundle
			ApiServer Bundle
		}
		Peers struct {
			CA        Bundle
			Universal Bundle
		}
	}

	ApiServer struct {
		Clients struct {
			CA                Bundle
			ControllerManager Bundle
			Scheduler         Bundle
			Proxy             Bundle
			ClusterAdmin      Bundle
			Wormhole          Bundle
		}
		Nodes struct {
			CA        Bundle
			Universal Bundle
		}
	}

	Kubelet struct {
		Clients struct {
			CA        Bundle
			ApiServer Bundle
		}
	}

	TLS struct {
		CA        Bundle
		ApiServer Bundle
		Wormhole  Bundle
	}

	Aggregation struct {
		CA         Bundle
		Aggregator Bundle
	}
}

func (c *Certificates) toMap() map[string]string {
	bundles := c.all()
	result := make(map[string]string, len(bundles)*2)
	for _, bundle := range bundles {
		result[bundle.NameForCert()] = string(certutil.EncodeCertPEM(bundle.Certificate))
		result[bundle.NameForKey()] = string(certutil.EncodePrivateKeyPEM(bundle.PrivateKey))
	}
	return result
}

func (c *Certificates) MarshalYAML() (interface{}, error) {
	return c.toMap(), nil
}

func NewBundle(key, cert []byte) (Bundle, error) {
	certificates, err := certutil.ParseCertsPEM(cert)
	if err != nil {
		return Bundle{}, err
	}
	if len(certificates) < 1 {
		return Bundle{}, errors.New("No certificates found")
	}
	k, err := certutil.ParsePrivateKeyPEM(key)
	if err != nil {
		return Bundle{}, err
	}
	rsaKey, isRSAKey := k.(*rsa.PrivateKey)
	if !isRSAKey {
		return Bundle{}, errors.New("Key does not seem to be of type RSA")
	}

	return Bundle{PrivateKey: rsaKey, Certificate: certificates[0]}, nil
}

func (b *Bundle) basename() string {
	stem := b.Certificate.Issuer.CommonName
	suffix := b.Certificate.Subject.CommonName

	if b.Certificate.IsCA {
		stem = b.Certificate.Subject.CommonName
		suffix = "ca"
	}

	return sanitize.BaseName(strings.ToLower(fmt.Sprintf("%s-%s", stem, suffix)))
}

func (b *Bundle) NameForKey() string {
	return fmt.Sprintf("%s-key.pem", b.basename())
}
func (b *Bundle) NameForCert() string {
	return fmt.Sprintf("%s.pem", b.basename())
}

type Config struct {
	Sign               string
	Organization       []string
	OrganizationalUnit []string
	Province           []string
	Locality           []string
	AltNames           AltNames
	Usages             []x509.ExtKeyUsage
	ValidFor           time.Duration
}

type AltNames struct {
	DNSNames []string
	IPs      []net.IP
}

func (c Certificates) all() []Bundle {
	return []Bundle{
		c.Etcd.Clients.CA,
		c.Etcd.Clients.ApiServer,
		c.Etcd.Peers.CA,
		c.Etcd.Peers.Universal,
		c.ApiServer.Clients.CA,
		c.ApiServer.Clients.ControllerManager,
		c.ApiServer.Clients.Scheduler,
		c.ApiServer.Clients.Proxy,
		c.ApiServer.Clients.ClusterAdmin,
		c.ApiServer.Clients.Wormhole,
		c.ApiServer.Nodes.CA,
		c.ApiServer.Nodes.Universal,
		c.Kubelet.Clients.CA,
		c.Kubelet.Clients.ApiServer,
		c.TLS.CA,
		c.TLS.ApiServer,
		c.TLS.Wormhole,
		c.Aggregation.CA,
		c.Aggregation.Aggregator,
	}
}

func CreateCertificates(kluster *v1.Kluster, apiURL, authURL, domain string) (map[string]string, error) {
	certs := &Certificates{}
	apiIP, err := kluster.ApiServiceIP()
	if err != nil {
		return nil, err
	}
	createCA(kluster.Name, "Etcd Clients", &certs.Etcd.Clients.CA)
	createCA(kluster.Name, "Etcd Peers", &certs.Etcd.Peers.CA)
	createCA(kluster.Name, "ApiServer Clients", &certs.ApiServer.Clients.CA)
	createCA(kluster.Name, "ApiServer Nodes", &certs.ApiServer.Nodes.CA)
	createCA(kluster.Name, "Kubelet Clients", &certs.Kubelet.Clients.CA)
	createCA(kluster.Name, "TLS", &certs.TLS.CA)
	createCA(kluster.Name, "Aggregation", &certs.Aggregation.CA)

	certs.Etcd.Clients.ApiServer = certs.signEtcdClient("apiserver")
	certs.Etcd.Peers.Universal = certs.signEtcdPeer("universal")
	certs.ApiServer.Clients.ClusterAdmin = certs.signApiServerClient("cluster-admin", "system:masters")
	certs.ApiServer.Clients.ControllerManager = certs.signApiServerClient("system:kube-controller-manager")
	certs.ApiServer.Clients.Proxy = certs.signApiServerClient("system:kube-proxy")
	certs.ApiServer.Clients.Scheduler = certs.signApiServerClient("system:kube-scheduler")
	certs.ApiServer.Clients.Wormhole = certs.signApiServerClient("kubernikus:wormhole")
	certs.ApiServer.Nodes.Universal = certs.signApiServerNode("universal")
	certs.Kubelet.Clients.ApiServer = certs.signKubeletClient("apiserver")
	certs.TLS.ApiServer = certs.signTLS("apiserver",
		[]string{"kubernetes", "kubernetes.default", "apiserver", kluster.Name, fmt.Sprintf("%v.%v", kluster.Name, domain)},
		[]net.IP{net.IPv4(127, 0, 0, 1), apiIP})
	certs.TLS.Wormhole = certs.signTLS("wormhole",
		[]string{fmt.Sprintf("%v-wormhole.%v", kluster.Name, domain)}, []net.IP{})
	certs.Aggregation.Aggregator = certs.signAggregation("aggregator")

	return certs.toMap(), nil
}

func (c Certificates) signEtcdClient(name string) Bundle {
	config := Config{
		Sign:   name,
		Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
	}
	return c.Etcd.Clients.CA.Sign(config)
}

func (c Certificates) signEtcdPeer(name string) Bundle {
	config := Config{
		Sign:   name,
		Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
	}
	return c.Etcd.Peers.CA.Sign(config)
}

func (c Certificates) signApiServerClient(name string, groups ...string) Bundle {
	config := Config{
		Sign:         name,
		Organization: groups,
		Usages:       []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
	}
	return c.ApiServer.Clients.CA.Sign(config)
}

func (c Certificates) signApiServerNode(name string) Bundle {
	config := Config{
		Sign:         name,
		Organization: []string{"system:nodes"},
		Usages:       []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
	}
	return c.ApiServer.Nodes.CA.Sign(config)
}

func (c Certificates) signKubeletClient(name string, groups ...string) Bundle {
	config := Config{
		Sign:         name,
		Organization: groups,
		Usages:       []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
	}
	return c.Kubelet.Clients.CA.Sign(config)
}

func (c Certificates) signTLS(name string, dnsNames []string, ips []net.IP) Bundle {
	config := Config{
		Sign:   name,
		Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		AltNames: AltNames{
			DNSNames: dnsNames,
			IPs:      ips,
		},
	}
	return c.TLS.CA.Sign(config)
}

func (c Certificates) signAggregation(name string) Bundle {
	config := Config{
		Sign:   name,
		Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
	}
	return c.Aggregation.CA.Sign(config)
}

func createCA(klusterName, name string, bundle *Bundle) {
	bundle.PrivateKey, _ = certutil.NewPrivateKey()

	now := time.Now()
	tmpl := x509.Certificate{
		SerialNumber: new(big.Int).SetInt64(0),
		Subject: pkix.Name{
			CommonName:         name,
			OrganizationalUnit: []string{CA_ISSUER_KUBERNIKUS_IDENTIFIER_0, CA_ISSUER_KUBERNIKUS_IDENTIFIER_1, klusterName},
		},
		NotBefore:             now.UTC(),
		NotAfter:              now.Add(caValidity).UTC(),
		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
		BasicConstraintsValid: true,
		IsCA: true,
	}

	certDERBytes, _ := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, bundle.PrivateKey.Public(), bundle.PrivateKey)
	bundle.Certificate, _ = x509.ParseCertificate(certDERBytes)
}

func (ca Bundle) Sign(config Config) Bundle {
	if !ca.Certificate.IsCA {
		panic("You can't use this certificate for signing. It's not a CA...")
	}

	if config.ValidFor == 0 {
		config.ValidFor = defaultCertValidity
	}

	key, _ := certutil.NewPrivateKey()
	serial, _ := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64))

	certTmpl := x509.Certificate{
		Subject: pkix.Name{
			CommonName:         config.Sign,
			Organization:       config.Organization,
			OrganizationalUnit: config.OrganizationalUnit,
			Province:           config.Province,
			Locality:           config.Locality,
		},
		DNSNames:     config.AltNames.DNSNames,
		IPAddresses:  config.AltNames.IPs,
		SerialNumber: serial,
		NotBefore:    ca.Certificate.NotBefore,
		NotAfter:     time.Now().Add(config.ValidFor).UTC(),
		KeyUsage:     x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
		ExtKeyUsage:  config.Usages,
	}

	certDERBytes, _ := x509.CreateCertificate(rand.Reader, &certTmpl, ca.Certificate, key.Public(), ca.PrivateKey)

	cert, _ := x509.ParseCertificate(certDERBytes)

	return Bundle{cert, key}
}
<commit_msg>add missing alt-name to apiserver cert<commit_after>package util

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"errors"
	"fmt"
	"math"
	"math/big"
	"net"
	"strings"
	"time"

	"github.com/kennygrant/sanitize"
	certutil "k8s.io/client-go/util/cert"

	"github.com/sapcc/kubernikus/pkg/apis/kubernikus/v1"
)

const (
	//by default we generate certs with 2 year validity
	defaultCertValidity = 2 * time.Hour * 24 * 365
	//out CAs are valid for 10 years
	caValidity = 10 * time.Hour * 24 * 365
)

type Bundle struct {
	Certificate *x509.Certificate
	PrivateKey  *rsa.PrivateKey
}

type Certificates struct {
	Etcd struct {
		Clients struct {
			CA        Bundle
			ApiServer Bundle
		}
		Peers struct {
			CA        Bundle
			Universal Bundle
		}
	}

	ApiServer struct {
		Clients struct {
			CA                Bundle
			ControllerManager Bundle
			Scheduler         Bundle
			Proxy             Bundle
			ClusterAdmin      Bundle
			Wormhole          Bundle
		}
		Nodes struct {
			CA        Bundle
			Universal Bundle
		}
	}

	Kubelet struct {
		Clients struct {
			CA        Bundle
			ApiServer Bundle
		}
	}

	TLS struct {
		CA        Bundle
		ApiServer Bundle
		Wormhole  Bundle
	}

	Aggregation struct {
		CA         Bundle
		Aggregator Bundle
	}
}

func (c *Certificates) toMap() map[string]string {
	bundles := c.all()
	result := make(map[string]string, len(bundles)*2)
	for _, bundle := range bundles {
		result[bundle.NameForCert()] = string(certutil.EncodeCertPEM(bundle.Certificate))
		result[bundle.NameForKey()] = string(certutil.EncodePrivateKeyPEM(bundle.PrivateKey))
	}
	return result
}

func (c *Certificates) MarshalYAML() (interface{}, error) {
	return c.toMap(), nil
}

func NewBundle(key, cert []byte) (Bundle, error) {
	certificates, err := certutil.ParseCertsPEM(cert)
	if err != nil {
		return Bundle{}, err
	}
	if len(certificates) < 1 {
		return Bundle{}, errors.New("No certificates found")
	}
	k, err := certutil.ParsePrivateKeyPEM(key)
	if err != nil {
		return Bundle{}, err
	}
	rsaKey, isRSAKey := k.(*rsa.PrivateKey)
	if !isRSAKey {
		return Bundle{}, errors.New("Key does not seem to be of type RSA")
	}

	return Bundle{PrivateKey: rsaKey, Certificate: certificates[0]}, nil
}

func (b *Bundle) basename() string {
	stem := b.Certificate.Issuer.CommonName
	suffix := b.Certificate.Subject.CommonName

	if b.Certificate.IsCA {
		stem = b.Certificate.Subject.CommonName
		suffix = "ca"
	}

	return sanitize.BaseName(strings.ToLower(fmt.Sprintf("%s-%s", stem, suffix)))
}

func (b *Bundle) NameForKey() string {
	return fmt.Sprintf("%s-key.pem", b.basename())
}
func (b *Bundle) NameForCert() string {
	return fmt.Sprintf("%s.pem", b.basename())
}

type Config struct {
	Sign               string
	Organization       []string
	OrganizationalUnit []string
	Province           []string
	Locality           []string
	AltNames           AltNames
	Usages             []x509.ExtKeyUsage
	ValidFor           time.Duration
}

type AltNames struct {
	DNSNames []string
	IPs      []net.IP
}

func (c Certificates) all() []Bundle {
	return []Bundle{
		c.Etcd.Clients.CA,
		c.Etcd.Clients.ApiServer,
		c.Etcd.Peers.CA,
		c.Etcd.Peers.Universal,
		c.ApiServer.Clients.CA,
		c.ApiServer.Clients.ControllerManager,
		c.ApiServer.Clients.Scheduler,
		c.ApiServer.Clients.Proxy,
		c.ApiServer.Clients.ClusterAdmin,
		c.ApiServer.Clients.Wormhole,
		c.ApiServer.Nodes.CA,
		c.ApiServer.Nodes.Universal,
		c.Kubelet.Clients.CA,
		c.Kubelet.Clients.ApiServer,
		c.TLS.CA,
		c.TLS.ApiServer,
		c.TLS.Wormhole,
		c.Aggregation.CA,
		c.Aggregation.Aggregator,
	}
}

func CreateCertificates(kluster *v1.Kluster, apiURL, authURL, domain string) (map[string]string, error) {
	certs := &Certificates{}
	apiIP, err := kluster.ApiServiceIP()
	if err != nil {
		return nil, err
	}
	createCA(kluster.Name, "Etcd Clients", &certs.Etcd.Clients.CA)
	createCA(kluster.Name, "Etcd Peers", &certs.Etcd.Peers.CA)
	createCA(kluster.Name, "ApiServer Clients", &certs.ApiServer.Clients.CA)
	createCA(kluster.Name, "ApiServer Nodes", &certs.ApiServer.Nodes.CA)
	createCA(kluster.Name, "Kubelet Clients", &certs.Kubelet.Clients.CA)
	createCA(kluster.Name, "TLS", &certs.TLS.CA)
	createCA(kluster.Name, "Aggregation", &certs.Aggregation.CA)

	certs.Etcd.Clients.ApiServer = certs.signEtcdClient("apiserver")
	certs.Etcd.Peers.Universal = certs.signEtcdPeer("universal")
	certs.ApiServer.Clients.ClusterAdmin = certs.signApiServerClient("cluster-admin", "system:masters")
	certs.ApiServer.Clients.ControllerManager = certs.signApiServerClient("system:kube-controller-manager")
	certs.ApiServer.Clients.Proxy = certs.signApiServerClient("system:kube-proxy")
	certs.ApiServer.Clients.Scheduler = certs.signApiServerClient("system:kube-scheduler")
	certs.ApiServer.Clients.Wormhole = certs.signApiServerClient("kubernikus:wormhole")
	certs.ApiServer.Nodes.Universal = certs.signApiServerNode("universal")
	certs.Kubelet.Clients.ApiServer = certs.signKubeletClient("apiserver")
	certs.TLS.ApiServer = certs.signTLS("apiserver",
		[]string{"kubernetes", "kubernetes.default", "kubernetes.default.svc", "apiserver", kluster.Name, fmt.Sprintf("%v.%v", kluster.Name, domain)},
		[]net.IP{net.IPv4(127, 0, 0, 1), apiIP})
	certs.TLS.Wormhole = certs.signTLS("wormhole",
		[]string{fmt.Sprintf("%v-wormhole.%v", kluster.Name, domain)}, []net.IP{})
	certs.Aggregation.Aggregator = certs.signAggregation("aggregator")

	return certs.toMap(), nil
}

func (c Certificates) signEtcdClient(name string) Bundle {
	config := Config{
		Sign:   name,
		Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
	}
	return c.Etcd.Clients.CA.Sign(config)
}

func (c Certificates) signEtcdPeer(name string) Bundle {
	config := Config{
		Sign:   name,
		Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
	}
	return c.Etcd.Peers.CA.Sign(config)
}

func (c Certificates) signApiServerClient(name string, groups ...string) Bundle {
	config := Config{
		Sign:         name,
		Organization: groups,
		Usages:       []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
	}
	return c.ApiServer.Clients.CA.Sign(config)
}

func (c Certificates) signApiServerNode(name string) Bundle {
	config := Config{
		Sign:         name,
		Organization: []string{"system:nodes"},
		Usages:       []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
	}
	return c.ApiServer.Nodes.CA.Sign(config)
}

func (c Certificates) signKubeletClient(name string, groups ...string) Bundle {
	config := Config{
		Sign:         name,
		Organization: groups,
		Usages:       []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
	}
	return c.Kubelet.Clients.CA.Sign(config)
}

func (c Certificates) signTLS(name string, dnsNames []string, ips []net.IP) Bundle {
	config := Config{
		Sign:   name,
		Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		AltNames: AltNames{
			DNSNames: dnsNames,
			IPs:      ips,
		},
	}
	return c.TLS.CA.Sign(config)
}

func (c Certificates) signAggregation(name string) Bundle {
	config := Config{
		Sign:   name,
		Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
	}
	return c.Aggregation.CA.Sign(config)
}

func createCA(klusterName, name string, bundle *Bundle) {
	bundle.PrivateKey, _ = certutil.NewPrivateKey()

	now := time.Now()
	tmpl := x509.Certificate{
		SerialNumber: new(big.Int).SetInt64(0),
		Subject: pkix.Name{
			CommonName:         name,
			OrganizationalUnit: []string{CA_ISSUER_KUBERNIKUS_IDENTIFIER_0, CA_ISSUER_KUBERNIKUS_IDENTIFIER_1, klusterName},
		},
		NotBefore:             now.UTC(),
		NotAfter:              now.Add(caValidity).UTC(),
		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
		BasicConstraintsValid: true,
		IsCA: true,
	}

	certDERBytes, _ := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, bundle.PrivateKey.Public(), bundle.PrivateKey)
	bundle.Certificate, _ = x509.ParseCertificate(certDERBytes)
}

func (ca Bundle) Sign(config Config) Bundle {
	if !ca.Certificate.IsCA {
		panic("You can't use this certificate for signing. It's not a CA...")
	}

	if config.ValidFor == 0 {
		config.ValidFor = defaultCertValidity
	}

	key, _ := certutil.NewPrivateKey()
	serial, _ := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64))

	certTmpl := x509.Certificate{
		Subject: pkix.Name{
			CommonName:         config.Sign,
			Organization:       config.Organization,
			OrganizationalUnit: config.OrganizationalUnit,
			Province:           config.Province,
			Locality:           config.Locality,
		},
		DNSNames:     config.AltNames.DNSNames,
		IPAddresses:  config.AltNames.IPs,
		SerialNumber: serial,
		NotBefore:    ca.Certificate.NotBefore,
		NotAfter:     time.Now().Add(config.ValidFor).UTC(),
		KeyUsage:     x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
		ExtKeyUsage:  config.Usages,
	}

	certDERBytes, _ := x509.CreateCertificate(rand.Reader, &certTmpl, ca.Certificate, key.Public(), ca.PrivateKey)

	cert, _ := x509.ParseCertificate(certDERBytes)

	return Bundle{cert, key}
}
<|endoftext|>