<commit_before>package coin

import (
	"context"
	"encoding/binary"
	"fmt"
	"log"
	"sync"
	"time"

	"github.com/temoto/alive"
	"github.com/temoto/vender/currency"
	"github.com/temoto/vender/hardware/mdb"
	"github.com/temoto/vender/hardware/money"
)

const (
	coinTypeCount = 16

	DelayErr  = 500 * time.Millisecond
	DelayNext = 200 * time.Millisecond

	RouteCashBox = 0
	RouteTubes   = 1
	RouteNotUsed = 2
	RouteReject  = 3
)

//go:generate stringer -type=Features
type Features uint32

const (
	FeatureAlternativePayout Features = 1 << iota
	FeatureExtendedDiagnostic
	FeatureControlledManualFillPayout
	FeatureFTL
)

type CoinAcceptor struct {
	mdb       mdb.Mdber
	byteOrder binary.ByteOrder

	// Indicates the value of the bill types 0 to 15.
	// These are final values including all scaling factors.
	coinTypeCredit []currency.Nominal

	featureLevel      uint8
	supportedFeatures Features

	internalScalingFactor int
	batch                 sync.Mutex
	ready                 chan struct{}
}

var (
	packetReset      = mdb.PacketFromHex("08")
	packetSetup      = mdb.PacketFromHex("09")
	packetTubeStatus = mdb.PacketFromHex("0a")
	packetPoll       = mdb.PacketFromHex("0b")
	packetExpIdent   = mdb.PacketFromHex("0f00")
)

var (
	ErrNoCredit      = fmt.Errorf("No Credit")
	ErrDoubleArrival = fmt.Errorf("Double Arrival")
	ErrCoinRouting   = fmt.Errorf("Coin Routing")
	ErrCoinJam       = fmt.Errorf("Coin Jam")
	ErrSlugs         = fmt.Errorf("Slugs")
)

// usage: defer coin.Batch()()
func (self *CoinAcceptor) Batch() func() {
	self.batch.Lock()
	return self.batch.Unlock
}

func (self *CoinAcceptor) Init(ctx context.Context, mdber mdb.Mdber) error {
	// TODO read config
	self.byteOrder = binary.BigEndian
	self.coinTypeCredit = make([]currency.Nominal, coinTypeCount)
	self.mdb = mdber
	self.internalScalingFactor = 1 // FIXME
	self.ready = make(chan struct{})
	// TODO maybe execute CommandReset?
	err := self.InitSequence()
	if err != nil {
		log.Printf("hardware/mdb/coin/InitSequence error=%s", err)
		// TODO maybe execute CommandReset?
	}
	return err
}

func (self *CoinAcceptor) SupportedNominals() []currency.Nominal {
	ns := make([]currency.Nominal, 0, len(self.coinTypeCredit))
	for _, n := range self.coinTypeCredit {
		if n > 0 {
			ns = append(ns, n)
		}
	}
	return ns
}

func (self *CoinAcceptor) Run(ctx context.Context, a *alive.Alive, ch chan<- money.PollResult) {
	stopch := a.StopChan()
	for a.IsRunning() {
		pr := self.CommandPoll()
		select {
		case ch <- pr:
		case <-stopch:
			return
		}
		select {
		case <-time.After(pr.Delay):
		case <-stopch:
			return
		}
	}
}

func (self *CoinAcceptor) InitSequence() error {
	defer self.Batch()()

	err := self.CommandSetup()
	if err != nil {
		return err
	}
	if err = self.CommandExpansionIdentification(); err != nil {
		return err
	}
	if err = self.CommandFeatureEnable(FeatureExtendedDiagnostic); err != nil {
		return err
	}
	if err = self.CommandExpansionSendDiagStatus(); err != nil {
		return err
	}
	if err = self.CommandTubeStatus(); err != nil {
		return err
	}
	// TODO read config
	if err = self.CommandCoinType(0xffff, 0xffff); err != nil {
		return err
	}
	return nil
}

func (self *CoinAcceptor) CommandReset() error {
	response := new(mdb.Packet)
	return self.mdb.Tx(packetReset, response)
}

func (self *CoinAcceptor) CommandSetup() error {
	const expectLengthMin = 7
	request := packetSetup
	response := new(mdb.Packet)
	err := self.mdb.Tx(request, response)
	if err != nil {
		log.Printf("mdb request=%s err=%v", request.Format(), err)
		return err
	}
	log.Printf("setup response=(%d)%s", response.Len(), response.Format())
	bs := response.Bytes()
	if len(bs) < expectLengthMin {
		return fmt.Errorf("hardware/mdb/coin SETUP response=%s expected >= %d bytes", response.Format(), expectLengthMin)
	}
	scalingFactor := bs[3]
	for i, sf := range bs[7:] {
		n := currency.Nominal(sf) * currency.Nominal(scalingFactor) * currency.Nominal(self.internalScalingFactor)
		log.Printf("i=%d sf=%d nominal=%s", i, sf, currency.Amount(n).Format100I())
		self.coinTypeCredit[i] = n
	}
	self.featureLevel = bs[0]
	log.Printf("Changer Feature Level: %d", self.featureLevel)
	log.Printf("Country / Currency Code: %x", bs[1:3])
	log.Printf("Coin Scaling Factor: %d", scalingFactor)
	log.Printf("Decimal Places: %d", bs[4])
	log.Printf("Coin Type Routing: %d", self.byteOrder.Uint16(bs[5:7]))
	log.Printf("Coin Type Credit: %x %#v", bs[7:], self.coinTypeCredit)
	return nil
}

func (self *CoinAcceptor) CommandPoll() (result money.PollResult) {
	defer func() {
		if result.Ready() {
			select {
			case self.ready <- struct{}{}:
			default:
			}
		}
	}()

	now := time.Now()
	response := new(mdb.Packet)
	savedebug := self.mdb.SetDebug(false)
	err := self.mdb.Tx(packetPoll, response)
	self.mdb.SetDebug(savedebug)
	result.Time = now
	result.Delay = DelayNext
	if err != nil {
		result.Error = err
		result.Delay = DelayErr
		return result
	}
	if response.Len() == 0 {
		return result
	}
	result.Items = make([]money.PollItem, 0, response.Len())
	log.Printf("poll response=%s", response.Format())
	bs := response.Bytes()
	pi := money.PollItem{}
	skip := false
	for i, b := range bs {
		if skip {
			skip = false
			continue
		}
		b2 := byte(0)
		if i+1 < len(bs) {
			b2 = bs[i+1]
		}
		pi, skip = self.parsePollItem(b, b2)
		result.Items = append(result.Items, pi)
	}
	return result
}

func (self *CoinAcceptor) CommandTubeStatus() error {
	const expectLengthMin = 2
	request := packetTubeStatus
	response := new(mdb.Packet)
	err := self.mdb.Tx(request, response)
	if err != nil {
		log.Printf("mdb request=%s err=%v", request.Format(), err)
		return err
	}
	log.Printf("tubestatus response=(%d)%s", response.Len(), response.Format())
	bs := response.Bytes()
	if len(bs) < expectLengthMin {
		return fmt.Errorf("hardware/mdb/coin TUBE money.Status response=%s expected >= %d bytes", response.Format(), expectLengthMin)
	}
	full := self.byteOrder.Uint16(bs[0:2])
	counts := bs[2:18]
	log.Printf("tubestatus full=%b counts=%v", full, counts)
	// TODO use full,counts
	_ = full
	_ = counts
	return nil
}

func (self *CoinAcceptor) CommandCoinType(accept, dispense uint16) error {
	buf := [5]byte{0x0c}
	self.byteOrder.PutUint16(buf[1:], accept)
	self.byteOrder.PutUint16(buf[3:], dispense)
	request := mdb.PacketFromBytes(buf[:])
	response := new(mdb.Packet)
	err := self.mdb.Tx(request, response)
	if err != nil {
		log.Printf("mdb request=%s err=%v", request.Format(), err)
		return err
	}
	return nil
}

func (self *CoinAcceptor) CommandDispense(nominal currency.Nominal, count uint8) error {
	if count >= 16 {
		return fmt.Errorf("CommandDispense count=%d overflow >=16", count)
	}
	coinType, err := self.nominalCoinType(nominal)
	if err != nil {
		return err
	}

	response := new(mdb.Packet)
	request := mdb.PacketFromBytes([]byte{0x0d, (count << 4) + coinType})
	<-self.ready
	err = self.mdb.Tx(request, response)
	if err != nil {
		log.Printf("mdb request=%s err=%v", request.Format(), err)
		return err
	}
	return nil
}

func (self *CoinAcceptor) CommandExpansionIdentification() error {
	const expectLength = 33
	request := packetExpIdent
	response := new(mdb.Packet)
	err := self.mdb.Tx(request, response)
	if err != nil {
		log.Printf("mdb request=%s err=%v", request.Format(), err)
		return err
	}
	log.Printf("setup response=(%d)%s", response.Len(), response.Format())
	bs := response.Bytes()
	if len(bs) < expectLength {
		return fmt.Errorf("hardware/mdb/coin EXPANSION IDENTIFICATION response=%s expected %d bytes", response.Format(), expectLength)
	}
	self.supportedFeatures = Features(self.byteOrder.Uint32(bs[29:]))
	log.Printf("Supported features: %b", self.supportedFeatures)
	return nil
}

func (self *CoinAcceptor) CommandFeatureEnable(requested Features) error {
	f := requested & self.supportedFeatures
	buf := [6]byte{0x0f, 0x01}
	self.byteOrder.PutUint32(buf[2:], uint32(f))
	request := mdb.PacketFromBytes(buf[:])
	response := new(mdb.Packet)
	err := self.mdb.Tx(request, response)
	if err != nil {
		log.Printf("mdb request=%s err=%v", request.Format(), err)
		return err
	}
	return nil
}

func (self *CoinAcceptor) CommandExpansionSendDiagStatus() error {
	if self.supportedFeatures&FeatureExtendedDiagnostic == 0 {
		log.Printf("CommandExpansionSendDiagStatus feature is not supported")
		return nil
	}
	self.mdb.TxDebug(mdb.PacketFromHex("0f05"), true) // 0f05 EXPANSION SEND DIAG money.Status
	return nil
}

func (self *CoinAcceptor) coinTypeNominal(b byte) currency.Nominal {
	if b >= coinTypeCount {
		log.Printf("invalid coin type: %d", b)
		return 0
	}
	return self.coinTypeCredit[b]
}

func (self *CoinAcceptor) nominalCoinType(nominal currency.Nominal) (byte, error) {
	for ct, n := range self.coinTypeCredit {
		if n == nominal {
			return byte(ct), nil
		}
	}
	return 0, fmt.Errorf("Unknown nominal %s", currency.Amount(nominal).Format100I())
}

func (self *CoinAcceptor) parsePollItem(b, b2 byte) (money.PollItem, bool) {
	switch b {
	case 0x01: // Escrow request
		return money.PollItem{Status: money.StatusReturnRequest}, false
	case 0x02: // Changer Payout Busy
		return money.PollItem{Status: money.StatusBusy}, false
	case 0x03: // No Credit
		return money.PollItem{Status: money.StatusError, Error: ErrNoCredit}, false
	case 0x04: // Defective Tube Sensor
		return money.PollItem{Status: money.StatusFatal, Error: money.ErrSensor}, false
	case 0x05: // Double Arrival
		return money.PollItem{Status: money.StatusError, Error: ErrDoubleArrival}, false
	case 0x06: // Acceptor Unplugged
		return money.PollItem{Status: money.StatusFatal, Error: money.ErrNoStorage}, false
	case 0x07: // Tube Jam
		return money.PollItem{Status: money.StatusFatal, Error: money.ErrJam}, false
	case 0x08: // ROM checksum error
		return money.PollItem{Status: money.StatusFatal, Error: money.ErrROMChecksum}, false
	case 0x09: // Coin Routing Error
		return money.PollItem{Status: money.StatusError, Error: ErrCoinRouting}, false
	case 0x0a: // Changer Busy
		return money.PollItem{Status: money.StatusBusy}, false
	case 0x0b: // Changer was Reset
		return money.PollItem{Status: money.StatusWasReset}, false
	case 0x0c: // Coin Jam
		return money.PollItem{Status: money.StatusFatal, Error: ErrCoinJam}, false
	case 0x0d: // Possible Credited Coin Removal
		return money.PollItem{Status: money.StatusError, Error: money.ErrFraud}, false
	}

	if b&0x80 != 0 { // Coins Dispensed Manually
		// b=1yyyxxxx b2=number of coins in tube
		// yyy = coins dispensed
		// xxxx = coin type
		count := (b >> 4) & 7
		nominal := self.coinTypeNominal(b & 0xf)
		return money.PollItem{Status: money.StatusDispensed, DataNominal: nominal, DataCount: count}, true
	}
	if b&0x7f == b { // Coins Deposited
		// b=01yyxxxx b2=number of coins in tube
		// yy = coin routing
		// xxxx = coin type
		routing := (b >> 4) & 3
		if routing > 3 {
			panic("code error")
		}
		nominal := self.coinTypeNominal(b & 0xf)
		return money.PollItem{Status: money.StatusCredit, DataNominal: nominal, DataCount: 1}, true
	}
	if b&0x3f == b { // Slug count
		slugs := b & 0x1f
		log.Printf("Number of slugs: %d", slugs)
		return money.PollItem{Status: money.StatusInfo, Error: ErrSlugs, DataCount: slugs}, false
	}

	err := fmt.Errorf("parsePollItem unknown=%x", b)
	log.Print(err)
	return money.PollItem{Status: money.StatusFatal, Error: err}, false
}
<commit_msg>double arrival is not error<commit_after>package coin

import (
	"context"
	"encoding/binary"
	"fmt"
	"log"
	"sync"
	"time"

	"github.com/temoto/alive"
	"github.com/temoto/vender/currency"
	"github.com/temoto/vender/hardware/mdb"
	"github.com/temoto/vender/hardware/money"
)

const (
	coinTypeCount = 16

	DelayErr  = 500 * time.Millisecond
	DelayNext = 200 * time.Millisecond

	RouteCashBox = 0
	RouteTubes   = 1
	RouteNotUsed = 2
	RouteReject  = 3
)

//go:generate stringer -type=Features
type Features uint32

const (
	FeatureAlternativePayout Features = 1 << iota
	FeatureExtendedDiagnostic
	FeatureControlledManualFillPayout
	FeatureFTL
)

type CoinAcceptor struct {
	mdb       mdb.Mdber
	byteOrder binary.ByteOrder

	// Indicates the value of the bill types 0 to 15.
	// These are final values including all scaling factors.
	coinTypeCredit []currency.Nominal

	featureLevel      uint8
	supportedFeatures Features

	internalScalingFactor int
	batch                 sync.Mutex
	ready                 chan struct{}
}

var (
	packetReset      = mdb.PacketFromHex("08")
	packetSetup      = mdb.PacketFromHex("09")
	packetTubeStatus = mdb.PacketFromHex("0a")
	packetPoll       = mdb.PacketFromHex("0b")
	packetExpIdent   = mdb.PacketFromHex("0f00")
)

var (
	ErrNoCredit      = fmt.Errorf("No Credit")
	ErrDoubleArrival = fmt.Errorf("Double Arrival")
	ErrCoinRouting   = fmt.Errorf("Coin Routing")
	ErrCoinJam       = fmt.Errorf("Coin Jam")
	ErrSlugs         = fmt.Errorf("Slugs")
)

// usage: defer coin.Batch()()
func (self *CoinAcceptor) Batch() func() {
	self.batch.Lock()
	return self.batch.Unlock
}

func (self *CoinAcceptor) Init(ctx context.Context, mdber mdb.Mdber) error {
	// TODO read config
	self.byteOrder = binary.BigEndian
	self.coinTypeCredit = make([]currency.Nominal, coinTypeCount)
	self.mdb = mdber
	self.internalScalingFactor = 1 // FIXME
	self.ready = make(chan struct{})
	// TODO maybe execute CommandReset?
	err := self.InitSequence()
	if err != nil {
		log.Printf("hardware/mdb/coin/InitSequence error=%s", err)
		// TODO maybe execute CommandReset?
	}
	return err
}

func (self *CoinAcceptor) SupportedNominals() []currency.Nominal {
	ns := make([]currency.Nominal, 0, len(self.coinTypeCredit))
	for _, n := range self.coinTypeCredit {
		if n > 0 {
			ns = append(ns, n)
		}
	}
	return ns
}

func (self *CoinAcceptor) Run(ctx context.Context, a *alive.Alive, ch chan<- money.PollResult) {
	stopch := a.StopChan()
	for a.IsRunning() {
		pr := self.CommandPoll()
		select {
		case ch <- pr:
		case <-stopch:
			return
		}
		select {
		case <-time.After(pr.Delay):
		case <-stopch:
			return
		}
	}
}

func (self *CoinAcceptor) InitSequence() error {
	defer self.Batch()()

	err := self.CommandSetup()
	if err != nil {
		return err
	}
	if err = self.CommandExpansionIdentification(); err != nil {
		return err
	}
	if err = self.CommandFeatureEnable(FeatureExtendedDiagnostic); err != nil {
		return err
	}
	if err = self.CommandExpansionSendDiagStatus(); err != nil {
		return err
	}
	if err = self.CommandTubeStatus(); err != nil {
		return err
	}
	// TODO read config
	if err = self.CommandCoinType(0xffff, 0xffff); err != nil {
		return err
	}
	return nil
}

func (self *CoinAcceptor) CommandReset() error {
	response := new(mdb.Packet)
	return self.mdb.Tx(packetReset, response)
}

func (self *CoinAcceptor) CommandSetup() error {
	const expectLengthMin = 7
	request := packetSetup
	response := new(mdb.Packet)
	err := self.mdb.Tx(request, response)
	if err != nil {
		log.Printf("mdb request=%s err=%v", request.Format(), err)
		return err
	}
	log.Printf("setup response=(%d)%s", response.Len(), response.Format())
	bs := response.Bytes()
	if len(bs) < expectLengthMin {
		return fmt.Errorf("hardware/mdb/coin SETUP response=%s expected >= %d bytes", response.Format(), expectLengthMin)
	}
	scalingFactor := bs[3]
	for i, sf := range bs[7:] {
		n := currency.Nominal(sf) * currency.Nominal(scalingFactor) * currency.Nominal(self.internalScalingFactor)
		log.Printf("i=%d sf=%d nominal=%s", i, sf, currency.Amount(n).Format100I())
		self.coinTypeCredit[i] = n
	}
	self.featureLevel = bs[0]
	log.Printf("Changer Feature Level: %d", self.featureLevel)
	log.Printf("Country / Currency Code: %x", bs[1:3])
	log.Printf("Coin Scaling Factor: %d", scalingFactor)
	log.Printf("Decimal Places: %d", bs[4])
	log.Printf("Coin Type Routing: %d", self.byteOrder.Uint16(bs[5:7]))
	log.Printf("Coin Type Credit: %x %#v", bs[7:], self.coinTypeCredit)
	return nil
}

func (self *CoinAcceptor) CommandPoll() (result money.PollResult) {
	defer func() {
		if result.Ready() {
			select {
			case self.ready <- struct{}{}:
			default:
			}
		}
	}()

	now := time.Now()
	response := new(mdb.Packet)
	savedebug := self.mdb.SetDebug(false)
	err := self.mdb.Tx(packetPoll, response)
	self.mdb.SetDebug(savedebug)
	result.Time = now
	result.Delay = DelayNext
	if err != nil {
		result.Error = err
		result.Delay = DelayErr
		return result
	}
	if response.Len() == 0 {
		return result
	}
	result.Items = make([]money.PollItem, 0, response.Len())
	log.Printf("poll response=%s", response.Format())
	bs := response.Bytes()
	pi := money.PollItem{}
	skip := false
	for i, b := range bs {
		if skip {
			skip = false
			continue
		}
		b2 := byte(0)
		if i+1 < len(bs) {
			b2 = bs[i+1]
		}
		pi, skip = self.parsePollItem(b, b2)
		result.Items = append(result.Items, pi)
	}
	return result
}

func (self *CoinAcceptor) CommandTubeStatus() error {
	const expectLengthMin = 2
	request := packetTubeStatus
	response := new(mdb.Packet)
	err := self.mdb.Tx(request, response)
	if err != nil {
		log.Printf("mdb request=%s err=%v", request.Format(), err)
		return err
	}
	log.Printf("tubestatus response=(%d)%s", response.Len(), response.Format())
	bs := response.Bytes()
	if len(bs) < expectLengthMin {
		return fmt.Errorf("hardware/mdb/coin TUBE money.Status response=%s expected >= %d bytes", response.Format(), expectLengthMin)
	}
	full := self.byteOrder.Uint16(bs[0:2])
	counts := bs[2:18]
	log.Printf("tubestatus full=%b counts=%v", full, counts)
	// TODO use full,counts
	_ = full
	_ = counts
	return nil
}

func (self *CoinAcceptor) CommandCoinType(accept, dispense uint16) error {
	buf := [5]byte{0x0c}
	self.byteOrder.PutUint16(buf[1:], accept)
	self.byteOrder.PutUint16(buf[3:], dispense)
	request := mdb.PacketFromBytes(buf[:])
	response := new(mdb.Packet)
	err := self.mdb.Tx(request, response)
	if err != nil {
		log.Printf("mdb request=%s err=%v", request.Format(), err)
		return err
	}
	return nil
}

func (self *CoinAcceptor) CommandDispense(nominal currency.Nominal, count uint8) error {
	if count >= 16 {
		return fmt.Errorf("CommandDispense count=%d overflow >=16", count)
	}
	coinType, err := self.nominalCoinType(nominal)
	if err != nil {
		return err
	}

	response := new(mdb.Packet)
	request := mdb.PacketFromBytes([]byte{0x0d, (count << 4) + coinType})
	<-self.ready
	err = self.mdb.Tx(request, response)
	if err != nil {
		log.Printf("mdb request=%s err=%v", request.Format(), err)
		return err
	}
	return nil
}

func (self *CoinAcceptor) CommandExpansionIdentification() error {
	const expectLength = 33
	request := packetExpIdent
	response := new(mdb.Packet)
	err := self.mdb.Tx(request, response)
	if err != nil {
		log.Printf("mdb request=%s err=%v", request.Format(), err)
		return err
	}
	log.Printf("setup response=(%d)%s", response.Len(), response.Format())
	bs := response.Bytes()
	if len(bs) < expectLength {
		return fmt.Errorf("hardware/mdb/coin EXPANSION IDENTIFICATION response=%s expected %d bytes", response.Format(), expectLength)
	}
	self.supportedFeatures = Features(self.byteOrder.Uint32(bs[29:]))
	log.Printf("Supported features: %b", self.supportedFeatures)
	return nil
}

func (self *CoinAcceptor) CommandFeatureEnable(requested Features) error {
	f := requested & self.supportedFeatures
	buf := [6]byte{0x0f, 0x01}
	self.byteOrder.PutUint32(buf[2:], uint32(f))
	request := mdb.PacketFromBytes(buf[:])
	response := new(mdb.Packet)
	err := self.mdb.Tx(request, response)
	if err != nil {
		log.Printf("mdb request=%s err=%v", request.Format(), err)
		return err
	}
	return nil
}

func (self *CoinAcceptor) CommandExpansionSendDiagStatus() error {
	if self.supportedFeatures&FeatureExtendedDiagnostic == 0 {
		log.Printf("CommandExpansionSendDiagStatus feature is not supported")
		return nil
	}
	self.mdb.TxDebug(mdb.PacketFromHex("0f05"), true) // 0f05 EXPANSION SEND DIAG money.Status
	return nil
}

func (self *CoinAcceptor) coinTypeNominal(b byte) currency.Nominal {
	if b >= coinTypeCount {
		log.Printf("invalid coin type: %d", b)
		return 0
	}
	return self.coinTypeCredit[b]
}

func (self *CoinAcceptor) nominalCoinType(nominal currency.Nominal) (byte, error) {
	for ct, n := range self.coinTypeCredit {
		if n == nominal {
			return byte(ct), nil
		}
	}
	return 0, fmt.Errorf("Unknown nominal %s", currency.Amount(nominal).Format100I())
}

func (self *CoinAcceptor) parsePollItem(b, b2 byte) (money.PollItem, bool) {
	switch b {
	case 0x01: // Escrow request
		return money.PollItem{Status: money.StatusReturnRequest}, false
	case 0x02: // Changer Payout Busy
		return money.PollItem{Status: money.StatusBusy}, false
	case 0x03: // No Credit
		return money.PollItem{Status: money.StatusError, Error: ErrNoCredit}, false
	case 0x04: // Defective Tube Sensor
		return money.PollItem{Status: money.StatusFatal, Error: money.ErrSensor}, false
	case 0x05: // Double Arrival
		return money.PollItem{Status: money.StatusInfo, Error: ErrDoubleArrival}, false
	case 0x06: // Acceptor Unplugged
		return money.PollItem{Status: money.StatusFatal, Error: money.ErrNoStorage}, false
	case 0x07: // Tube Jam
		return money.PollItem{Status: money.StatusFatal, Error: money.ErrJam}, false
	case 0x08: // ROM checksum error
		return money.PollItem{Status: money.StatusFatal, Error: money.ErrROMChecksum}, false
	case 0x09: // Coin Routing Error
		return money.PollItem{Status: money.StatusError, Error: ErrCoinRouting}, false
	case 0x0a: // Changer Busy
		return money.PollItem{Status: money.StatusBusy}, false
	case 0x0b: // Changer was Reset
		return money.PollItem{Status: money.StatusWasReset}, false
	case 0x0c: // Coin Jam
		return money.PollItem{Status: money.StatusFatal, Error: ErrCoinJam}, false
	case 0x0d: // Possible Credited Coin Removal
		return money.PollItem{Status: money.StatusError, Error: money.ErrFraud}, false
	}

	if b&0x80 != 0 { // Coins Dispensed Manually
		// b=1yyyxxxx b2=number of coins in tube
		// yyy = coins dispensed
		// xxxx = coin type
		count := (b >> 4) & 7
		nominal := self.coinTypeNominal(b & 0xf)
		return money.PollItem{Status: money.StatusDispensed, DataNominal: nominal, DataCount: count}, true
	}
	if b&0x7f == b { // Coins Deposited
		// b=01yyxxxx b2=number of coins in tube
		// yy = coin routing
		// xxxx = coin type
		routing := (b >> 4) & 3
		if routing > 3 {
			panic("code error")
		}
		nominal := self.coinTypeNominal(b & 0xf)
		return money.PollItem{Status: money.StatusCredit, DataNominal: nominal, DataCount: 1}, true
	}
	if b&0x3f == b { // Slug count
		slugs := b & 0x1f
		log.Printf("Number of slugs: %d", slugs)
		return money.PollItem{Status: money.StatusInfo, Error: ErrSlugs, DataCount: slugs}, false
	}

	err := fmt.Errorf("parsePollItem unknown=%x", b)
	log.Print(err)
	return money.PollItem{Status: money.StatusFatal, Error: err}, false
}
<|endoftext|>


<commit_before>package controller_manager

import (
	"strings"
	"time"

	g "github.com/onsi/ginkgo"
	o "github.com/onsi/gomega"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/kubernetes"

	exutil "github.com/openshift/origin/test/extended/util"
)

func waitForServiceAccountToken(client kubernetes.Interface, ns, name string, attempts int, interval time.Duration) (string, error) {
	for i := 0; i <= attempts; i++ {
		time.Sleep(interval)
		token, err := getServiceAccountToken(client, ns, name)
		if err != nil {
			return "", err
		}
		if len(token) > 0 {
			return token, nil
		}
	}
	return "", nil
}

func getServiceAccountToken(client kubernetes.Interface, ns, name string) (string, error) {
	secrets, err := client.CoreV1().Secrets(ns).List(metav1.ListOptions{})
	if err != nil {
		return "", err
	}
	for _, secret := range secrets.Items {
		if secret.Type == corev1.SecretTypeServiceAccountToken && secret.Annotations[corev1.ServiceAccountNameKey] == name {
			sa, err := client.CoreV1().ServiceAccounts(ns).Get(name, metav1.GetOptions{})
			if err != nil {
				return "", err
			}

			for _, ref := range sa.Secrets {
				if ref.Name == secret.Name {
					return string(secret.Data[corev1.ServiceAccountTokenKey]), nil
				}
			}

		}
	}

	return "", nil
}

var _ = g.Describe("[Feature:OpenShiftControllerManager]", func() {
	defer g.GinkgoRecover()
	oc := exutil.NewCLI("pull-secrets", exutil.KubeConfigPath())

	g.It("TestAutomaticCreationOfPullSecrets", func() {
		t := g.GinkgoT()

		clusterAdminKubeClient := oc.AdminKubeClient()
		saNamespace := oc.Namespace()
		saName := "default"

		// Get a service account token
		saToken, err := waitForServiceAccountToken(clusterAdminKubeClient, saNamespace, saName, 20, time.Second)
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}
		if len(saToken) == 0 {
			t.Errorf("token was not created")
		}

		// Get the matching dockercfg secret
		_, saPullSecret, err := waitForServiceAccountPullSecret(clusterAdminKubeClient, saNamespace, saName, 20, time.Second)
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}
		if len(saPullSecret) == 0 {
			t.Errorf("pull secret was not created")
		}

		imageConfig, err := oc.AdminConfigClient().ConfigV1().Images().Get("cluster", metav1.GetOptions{})
		o.Expect(err).NotTo(o.HaveOccurred())
		if !strings.Contains(saPullSecret, imageConfig.Status.InternalRegistryHostname) {
			t.Errorf("missing %q in %v", imageConfig.Status.InternalRegistryHostname, saPullSecret)
		}

		if len(imageConfig.Spec.ExternalRegistryHostnames) > 0 {
			if !strings.Contains(saPullSecret, imageConfig.Spec.ExternalRegistryHostnames[0]) {
				t.Errorf("missing %q in %v", imageConfig.Spec.ExternalRegistryHostnames[0], saPullSecret)
			}
		}

	})
})

func waitForServiceAccountPullSecret(client kubernetes.Interface, ns, name string, attempts int, interval time.Duration) (string, string, error) {
	for i := 0; i <= attempts; i++ {
		time.Sleep(interval)
		secretName, dockerCfg, err := getServiceAccountPullSecret(client, ns, name)
		if err != nil {
			return "", "", err
		}
		if len(dockerCfg) > 2 {
			return secretName, dockerCfg, nil
		}
	}
	return "", "", nil
}

func getServiceAccountPullSecret(client kubernetes.Interface, ns, name string) (string, string, error) {
	secrets, err := client.CoreV1().Secrets(ns).List(metav1.ListOptions{})
	if err != nil {
		return "", "", err
	}
	for _, secret := range secrets.Items {
		if secret.Type == corev1.SecretTypeDockercfg && secret.Annotations[corev1.ServiceAccountNameKey] == name {
			return secret.Name, string(secret.Data[corev1.DockerConfigKey]), nil
		}
	}
	return "", "", nil
}

var _ = g.Describe("[Feature:OpenShiftControllerManager]", func() {
	defer g.GinkgoRecover()
	oc := exutil.NewCLI("pull-secrets", exutil.KubeConfigPath())

	g.It("TestDockercfgTokenDeletedController", func() {
		t := g.GinkgoT()

		clusterAdminKubeClient := oc.AdminKubeClient()
		saNamespace := oc.Namespace()

		sa := &corev1.ServiceAccount{
			ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: saNamespace},
		}

		sa, err := clusterAdminKubeClient.CoreV1().ServiceAccounts(sa.Namespace).Create(sa)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		// Get the service account dockercfg secret's name
		dockercfgSecretName, _, err := waitForServiceAccountPullSecret(clusterAdminKubeClient, sa.Namespace, sa.Name, 20, time.Second)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if len(dockercfgSecretName) == 0 {
			t.Fatal("pull secret was not created")
		}

		// Get the matching secret's name
		dockercfgSecret, err := clusterAdminKubeClient.CoreV1().Secrets(sa.Namespace).Get(dockercfgSecretName, metav1.GetOptions{})
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		secretName := dockercfgSecret.Annotations["openshift.io/token-secret.name"]
		if len(secretName) == 0 {
			t.Fatal("secret was not created")
		}

		// Delete the service account's secret
		if err := clusterAdminKubeClient.CoreV1().Secrets(sa.Namespace).Delete(secretName, nil); err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		// Expect the matching dockercfg secret to also be deleted
		if err := wait.Poll(5*time.Second, 5*time.Minute, func() (bool, error) {
			_, err := clusterAdminKubeClient.CoreV1().Secrets(sa.Namespace).Get(
				dockercfgSecretName,
				metav1.GetOptions{},
			)
			return errors.IsNotFound(err), nil
		}); err != nil {
			t.Fatalf("waiting for secret deletion: %v", err)
		}
	})
})
<commit_msg>Bug 1765294: Disable flaking token delete test<commit_after>package controller_manager

import (
	"strings"
	"time"

	g "github.com/onsi/ginkgo"
	o "github.com/onsi/gomega"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/kubernetes"

	exutil "github.com/openshift/origin/test/extended/util"
)

func waitForServiceAccountToken(client kubernetes.Interface, ns, name string, attempts int, interval time.Duration) (string, error) {
	for i := 0; i <= attempts; i++ {
		time.Sleep(interval)
		token, err := getServiceAccountToken(client, ns, name)
		if err != nil {
			return "", err
		}
		if len(token) > 0 {
			return token, nil
		}
	}
	return "", nil
}

func getServiceAccountToken(client kubernetes.Interface, ns, name string) (string, error) {
	secrets, err := client.CoreV1().Secrets(ns).List(metav1.ListOptions{})
	if err != nil {
		return "", err
	}
	for _, secret := range secrets.Items {
		if secret.Type == corev1.SecretTypeServiceAccountToken && secret.Annotations[corev1.ServiceAccountNameKey] == name {
			sa, err := client.CoreV1().ServiceAccounts(ns).Get(name, metav1.GetOptions{})
			if err != nil {
				return "", err
			}

			for _, ref := range sa.Secrets {
				if ref.Name == secret.Name {
					return string(secret.Data[corev1.ServiceAccountTokenKey]), nil
				}
			}

		}
	}

	return "", nil
}

var _ = g.Describe("[Feature:OpenShiftControllerManager]", func() {
	defer g.GinkgoRecover()
	oc := exutil.NewCLI("pull-secrets", exutil.KubeConfigPath())

	g.It("TestAutomaticCreationOfPullSecrets", func() {
		t := g.GinkgoT()

		clusterAdminKubeClient := oc.AdminKubeClient()
		saNamespace := oc.Namespace()
		saName := "default"

		// Get a service account token
		saToken, err := waitForServiceAccountToken(clusterAdminKubeClient, saNamespace, saName, 20, time.Second)
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}
		if len(saToken) == 0 {
			t.Errorf("token was not created")
		}

		// Get the matching dockercfg secret
		_, saPullSecret, err := waitForServiceAccountPullSecret(clusterAdminKubeClient, saNamespace, saName, 20, time.Second)
		if err != nil {
			t.Errorf("unexpected error: %v", err)
		}
		if len(saPullSecret) == 0 {
			t.Errorf("pull secret was not created")
		}

		imageConfig, err := oc.AdminConfigClient().ConfigV1().Images().Get("cluster", metav1.GetOptions{})
		o.Expect(err).NotTo(o.HaveOccurred())
		if !strings.Contains(saPullSecret, imageConfig.Status.InternalRegistryHostname) {
			t.Errorf("missing %q in %v", imageConfig.Status.InternalRegistryHostname, saPullSecret)
		}

		if len(imageConfig.Spec.ExternalRegistryHostnames) > 0 {
			if !strings.Contains(saPullSecret, imageConfig.Spec.ExternalRegistryHostnames[0]) {
				t.Errorf("missing %q in %v", imageConfig.Spec.ExternalRegistryHostnames[0], saPullSecret)
			}
		}

	})
})

func waitForServiceAccountPullSecret(client kubernetes.Interface, ns, name string, attempts int, interval time.Duration) (string, string, error) {
	for i := 0; i <= attempts; i++ {
		time.Sleep(interval)
		secretName, dockerCfg, err := getServiceAccountPullSecret(client, ns, name)
		if err != nil {
			return "", "", err
		}
		if len(dockerCfg) > 2 {
			return secretName, dockerCfg, nil
		}
	}
	return "", "", nil
}

func getServiceAccountPullSecret(client kubernetes.Interface, ns, name string) (string, string, error) {
	secrets, err := client.CoreV1().Secrets(ns).List(metav1.ListOptions{})
	if err != nil {
		return "", "", err
	}
	for _, secret := range secrets.Items {
		if secret.Type == corev1.SecretTypeDockercfg && secret.Annotations[corev1.ServiceAccountNameKey] == name {
			return secret.Name, string(secret.Data[corev1.DockerConfigKey]), nil
		}
	}
	return "", "", nil
}

var _ = g.Describe("[Feature:OpenShiftControllerManager]", func() {
	defer g.GinkgoRecover()
	oc := exutil.NewCLI("pull-secrets", exutil.KubeConfigPath())

	g.It("TestDockercfgTokenDeletedController", func() {
		g.Skip("Bug 1765294: Pull secrets are not always being deleted when token is deleted. Disabling until root cause is fixed.")
		t := g.GinkgoT()

		clusterAdminKubeClient := oc.AdminKubeClient()
		saNamespace := oc.Namespace()

		sa := &corev1.ServiceAccount{
			ObjectMeta: metav1.ObjectMeta{Name: "sa1", Namespace: saNamespace},
		}

		sa, err := clusterAdminKubeClient.CoreV1().ServiceAccounts(sa.Namespace).Create(sa)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		// Get the service account dockercfg secret's name
		dockercfgSecretName, _, err := waitForServiceAccountPullSecret(clusterAdminKubeClient, sa.Namespace, sa.Name, 20, time.Second)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if len(dockercfgSecretName) == 0 {
			t.Fatal("pull secret was not created")
		}

		// Get the matching secret's name
		dockercfgSecret, err := clusterAdminKubeClient.CoreV1().Secrets(sa.Namespace).Get(dockercfgSecretName, metav1.GetOptions{})
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		secretName := dockercfgSecret.Annotations["openshift.io/token-secret.name"]
		if len(secretName) == 0 {
			t.Fatal("secret was not created")
		}

		// Delete the service account's secret
		if err := clusterAdminKubeClient.CoreV1().Secrets(sa.Namespace).Delete(secretName, nil); err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		// Expect the matching dockercfg secret to also be deleted
		if err := wait.Poll(5*time.Second, 5*time.Minute, func() (bool, error) {
			_, err := clusterAdminKubeClient.CoreV1().Secrets(sa.Namespace).Get(
				dockercfgSecretName,
				metav1.GetOptions{},
			)
			return errors.IsNotFound(err), nil
		}); err != nil {
			t.Fatalf("waiting for secret deletion: %v", err)
		}
	})
})
<|endoftext|>


<commit_before>package pgpkeys

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"strings"

	"github.com/hashicorp/go-cleanhttp"
	"golang.org/x/crypto/openpgp"
)

const (
	kbPrefix = "keybase:"
)

// FetchKeybasePubkeys fetches public keys from Keybase given a set of
// usernames, which are derived from correctly formatted input entries. It
// doesn't use their client code due to both the API and the fact that it is
// considered alpha and probably best not to rely on it.  The keys are returned
// as base64-encoded strings.
func FetchKeybasePubkeys(input []string) (map[string]string, error) {
	client := cleanhttp.DefaultClient()
	if client == nil {
		return nil, fmt.Errorf("unable to create an http client")
	}

	if len(input) == 0 {
		return nil, nil
	}

	usernames := make([]string, 0, len(input))
	for _, v := range input {
		if strings.HasPrefix(v, kbPrefix) {
			usernames = append(usernames, strings.TrimPrefix(v, kbPrefix))
		}
	}

	if len(usernames) == 0 {
		return nil, nil
	}

	ret := make(map[string]string, len(usernames))
	url := fmt.Sprintf("https://keybase.io/_/api/1.0/user/lookup.json?usernames=%s&fields=public_keys", strings.Join(usernames, ","))
	resp, err := client.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	type publicKeys struct {
		Primary struct {
			Bundle string
		}
	}

	type them struct {
		publicKeys `json:"public_keys"`
	}

	type kbResp struct {
		Status struct {
			Name string
		}
		Them []them
	}

	out := &kbResp{
		Them: []them{},
	}

	dec := json.NewDecoder(resp.Body)
	if err := dec.Decode(out); err != nil {
		return nil, err
	}

	if out.Status.Name != "OK" {
		return nil, fmt.Errorf("got non-OK response: %s", out.Status.Name)
	}

	if len(out.Them) != len(usernames) {
		return nil, fmt.Errorf("returned keys length does not match number of provided usernames")
	}

	var keyReader *bytes.Reader
	serializedEntity := bytes.NewBuffer(nil)
	for i, themVal := range out.Them {
		keyReader = bytes.NewReader([]byte(themVal.Primary.Bundle))
		entityList, err := openpgp.ReadArmoredKeyRing(keyReader)
		if err != nil {
			return nil, err
		}
		if len(entityList) != 1 {
			return nil, fmt.Errorf("primary key could not be parsed for user %s", usernames[i])
		}
		if entityList[0] == nil {
			return nil, fmt.Errorf("primary key was nil for user %s", usernames[i])
		}

		serializedEntity.Reset()
		err = entityList[0].Serialize(serializedEntity)
		if err != nil {
			return nil, fmt.Errorf("error serializing entity for user %s: %s", usernames[i], err)
		}

		// The API returns values in the same ordering requested, so this should properly match
		ret[kbPrefix+usernames[i]] = base64.StdEncoding.EncodeToString(serializedEntity.Bytes())
	}

	return ret, nil
}
<commit_msg>Add returning which user names could not be looked up<commit_after>package pgpkeys

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"strings"

	"github.com/hashicorp/go-cleanhttp"
	"golang.org/x/crypto/openpgp"
)

const (
	kbPrefix = "keybase:"
)

// FetchKeybasePubkeys fetches public keys from Keybase given a set of
// usernames, which are derived from correctly formatted input entries. It
// doesn't use their client code due to both the API and the fact that it is
// considered alpha and probably best not to rely on it.  The keys are returned
// as base64-encoded strings.
func FetchKeybasePubkeys(input []string) (map[string]string, error) {
	client := cleanhttp.DefaultClient()
	if client == nil {
		return nil, fmt.Errorf("unable to create an http client")
	}

	if len(input) == 0 {
		return nil, nil
	}

	usernames := make([]string, 0, len(input))
	for _, v := range input {
		if strings.HasPrefix(v, kbPrefix) {
			usernames = append(usernames, strings.TrimPrefix(v, kbPrefix))
		}
	}

	if len(usernames) == 0 {
		return nil, nil
	}

	ret := make(map[string]string, len(usernames))
	url := fmt.Sprintf("https://keybase.io/_/api/1.0/user/lookup.json?usernames=%s&fields=public_keys", strings.Join(usernames, ","))
	resp, err := client.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	type publicKeys struct {
		Primary struct {
			Bundle string
		}
	}

	type them struct {
		publicKeys `json:"public_keys"`
	}

	type kbResp struct {
		Status struct {
			Name string
		}
		Them []them
	}

	out := &kbResp{
		Them: []them{},
	}

	dec := json.NewDecoder(resp.Body)
	if err := dec.Decode(out); err != nil {
		return nil, err
	}

	if out.Status.Name != "OK" {
		return nil, fmt.Errorf("got non-OK response: %s", out.Status.Name)
	}

	missingNames := make([]string, 0, len(usernames))
	var keyReader *bytes.Reader
	serializedEntity := bytes.NewBuffer(nil)
	for i, themVal := range out.Them {
		if themVal.Primary.Bundle == "" {
			missingNames = append(missingNames, usernames[i])
			continue
		}
		keyReader = bytes.NewReader([]byte(themVal.Primary.Bundle))
		entityList, err := openpgp.ReadArmoredKeyRing(keyReader)
		if err != nil {
			return nil, err
		}
		if len(entityList) != 1 {
			return nil, fmt.Errorf("primary key could not be parsed for user %s", usernames[i])
		}
		if entityList[0] == nil {
			return nil, fmt.Errorf("primary key was nil for user %s", usernames[i])
		}

		serializedEntity.Reset()
		err = entityList[0].Serialize(serializedEntity)
		if err != nil {
			return nil, fmt.Errorf("error serializing entity for user %s: %s", usernames[i], err)
		}

		// The API returns values in the same ordering requested, so this should properly match
		ret[kbPrefix+usernames[i]] = base64.StdEncoding.EncodeToString(serializedEntity.Bytes())
	}

	if len(missingNames) > 0 {
		return nil, fmt.Errorf("unable to fetch keys for user(s) %s from keybase", strings.Join(missingNames, ","))
	}

	return ret, nil
}
<|endoftext|>


<commit_before>package main

import (
	"fann"
)

func main() {
	const numLayers = 3
	ann := fann.CreateStandart(numLayers, []uint32{2, 3, 1})
	ann.Foo()
}
<commit_msg>Added reading to simple<commit_after>package main

import (
	"fann"
)

func main() {
	const numLayers = 3
	const desiredError = 0.001
	const maxEpochs = 500000
	const epochsBetweenReports = 1000

	ann := fann.CreateStandart(numLayers, []uint32{2, 3, 1})
	ann.TrainOnFile("xor.data", maxEpochs, epochsBetweenReports, desiredError)
	ann.Foo()
}
<|endoftext|>


<commit_before>// Copyright 2019 Google LLC
//
// 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 fakestore provides a fake in-mem storage.
// The implementation is based on the current real implementation using Datastore.
// See /lib/dsstore/
// TODO: once we have a fake Datastore server, get rid of this.
package fakestore

import (
	"fmt"
	"sort"
	"time"

	"github.com/golang/protobuf/proto" /* copybara-comment */
	"google.golang.org/grpc/codes" /* copybara-comment */
	"google.golang.org/grpc/status" /* copybara-comment */
	"github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/storage" /* copybara-comment: storage */
)

// Rev gives the string value of a revision.
func Rev(rev int64) string {
	if rev == storage.LatestRev {
		return storage.LatestRevName
	}
	return fmt.Sprintf("%06d", rev)
}

// Store is a fake in-mem store of data.
type Store struct {
	// Information map.
	Information map[string]string
	// State is where the data resides.
	// Any block of code that wants to access the state reads it from the chan,
	// performs its operations, and then writes it back to the chan.
	// See https://bit.ly/37ANPk4
	State chan State
}

// New creates a new Store.
func New() *Store {
	f := &Store{State: make(chan State, 1)}
	state := State{
		Data:    make(Data),
		History: make(Data),
	}
	f.State <- state
	return f
}

// Tx creates a new transaction.
func (s *Store) Tx(update bool) (storage.Tx, error) {
	return NewTx(s.State, update), nil
}

// Info returns information about the storage.
func (s *Store) Info() map[string]string {
	return s.Information
}

// Exists checks if data item with given key exists.
func (s *Store) Exists(datatype, realm, user, id string, rev int64) (_ bool, ferr error) {
	ntx, err := s.Tx(false)
	if err != nil {
		return false, err
	}
	defer func() {
		err := ntx.Finish()
		if ferr == nil {
			ferr = err
		}
	}()

	ntx.(*Tx).mu.Lock()
	defer ntx.(*Tx).mu.Unlock()
	return s.exists(datatype, realm, user, id, rev, ntx.(*Tx).state)
}

func (s *Store) exists(datatype, realm, user, id string, rev int64, state State) (bool, error) {
	key := Key{datatype, realm, user, id, Rev(rev)}
	if _, ok := state.Data[key]; !ok {
		return false, nil
	}
	return true, nil
}

// Read reads a data item of a given key.
func (s *Store) Read(datatype, realm, user, id string, rev int64, content proto.Message) error {
	return s.ReadTx(datatype, realm, user, id, rev, content, nil)
}

// ReadTx reads a data item of a given key inside a transaction.
// Calls Read if transaction is nil.
func (s *Store) ReadTx(datatype, realm, user, id string, rev int64, content proto.Message, tx storage.Tx) (ferr error) {
	ntx := tx
	if ntx == nil {
		var err error
		ntx, err = s.Tx(false)
		if err != nil {
			return err
		}
		// We need to call finish for this transaction.
		// We need to update the error returned from the function if commit fails.
		defer func() {
			err := ntx.Finish()
			if ferr == nil {
				ferr = err
			}
		}()
	}

	ntx.(*Tx).mu.Lock()
	defer ntx.(*Tx).mu.Unlock()
	return s.read(datatype, realm, user, id, rev, content, ntx.(*Tx).state)
}

func (s *Store) read(datatype, realm, user, id string, rev int64, content proto.Message, state State) error {
	key := Key{datatype, realm, user, id, Rev(rev)}
	v, ok := state.Data[key]
	if !ok {
		return status.Errorf(codes.NotFound, "not found: %+v rev:%v", key, rev)
	}

	content.Reset()
	proto.Merge(content, v)
	return nil
}

// MultiReadTx reads a set of items matching the input parameters and filters.
// Returns total count and error.
//
// content will contain the items which
//   their key matches the provided datatype, realm, user (if realm/user are not "")
//   their value matches the provider filers
// Items are sorted by their key's user and id in ascending order.
// The type of the item's value should be typ.
// Last revision of the items is used.
//
// content's maps are keyed by user and id of the keys.
func (s *Store) MultiReadTx(
	datatype, realm, user string,
	filters [][]storage.Filter,
	offset, pageSize int,
	content map[string]map[string]proto.Message,
	typ proto.Message,
	tx storage.Tx,
) (_ int, ferr error) {
	ntx := tx
	if ntx == nil {
		var err error
		ntx, err = s.Tx(false)
		if err != nil {
			return 0, err
		}
		defer func() {
			err := ntx.Finish()
			if ferr == nil {
				ferr = err
			}
		}()
	}

	ntx.(*Tx).mu.Lock()
	defer ntx.(*Tx).mu.Unlock()
	return s.multiRead(datatype, realm, user, filters, offset, pageSize, content, typ, ntx.(*Tx).state)
}

func (s *Store) multiRead(
	datatype, realm, user string,
	filters [][]storage.Filter,
	offset, pageSize int,
	content map[string]map[string]proto.Message,
	typ proto.Message,
	state State,
) (int, error) {
	if content == nil {
		return 0, status.Error(codes.InvalidArgument, "content cannot be nil")
	}
	if len(content) != 0 {
		return 0, status.Error(codes.InvalidArgument, "content is not empty")
	}

	var res KVList
	for k, v := range state.Data {
		if k.Datatype != datatype {
			continue
		}
		if k.Realm != "" && k.Realm != realm {
			continue
		}
		if k.User != "" && k.User != user {
			continue
		}
		if k.Rev != storage.LatestRevName {
			continue
		}

		if !storage.MatchProtoFilters(filters, v) {
			continue
		}

		if proto.MessageReflect(v).Descriptor() != proto.MessageReflect(typ).Descriptor() {
			return 0, status.Errorf(codes.InvalidArgument, "bad type provided: %T, want %T", typ, v)
		}

		res = append(res, KV{k, v})
	}

	sort.Sort(res)

	i := offset
	for ; i < len(res) && i < offset+pageSize; i++ {
		k := res[i].K
		v := res[i].V
		if _, ok := content[k.User]; !ok {
			content[k.User] = make(map[string]proto.Message)
		}
		content[k.User][k.ID] = proto.Clone(v)
	}

	return i - offset, nil
}

// ReadHistory reads the history of a given key.
func (s *Store) ReadHistory(datatype, realm, user, id string, content *[]proto.Message) error {
	return s.ReadHistoryTx(datatype, realm, user, id, content, nil)
}

// ReadHistoryTx reads the history of a given key inside a transaction.
func (s *Store) ReadHistoryTx(datatype, realm, user, id string, content *[]proto.Message, tx storage.Tx) (ferr error) {
	ntx := tx
	if ntx == nil {
		var err error
		ntx, err = s.Tx(false)
		if err != nil {
			return err
		}
		defer func() {
			err := ntx.Finish()
			if ferr == nil {
				ferr = err
			}
		}()
	}

	ntx.(*Tx).mu.Lock()
	defer ntx.(*Tx).mu.Unlock()
	return s.readHistory(datatype, realm, user, id, content, ntx.(*Tx).state)
}

// readHistory reads the history of a given key inside a transaction.
func (s *Store) readHistory(datatype, realm, user, id string, content *[]proto.Message, state State) error {
	if content == nil {
		return status.Error(codes.NotFound, "content cannot be nil")
	}
	key := Key{datatype, realm, user, id, ""}

	var res []proto.Message
	for k, v := range state.History {
		k.Rev = ""
		if k != key {
			continue
		}
		res = append(res, proto.Clone(v))
	}
	*content = res
	return nil
}

// Write writes an item.
func (s *Store) Write(datatype, realm, user, id string, rev int64, content proto.Message, history proto.Message) error {
	return s.WriteTx(datatype, realm, user, id, rev, content, history, nil)
}

// WriteTx writes an item inside a transaction.
func (s *Store) WriteTx(datatype, realm, user, id string, rev int64, content proto.Message, history proto.Message, tx storage.Tx) (ferr error) {
	ntx := tx
	if ntx == nil {
		var err error
		ntx, err = s.Tx(true)
		if err != nil {
			return err
		}
		defer func() {
			err := ntx.Finish()
			if ferr == nil {
				ferr = err
			}
		}()
	}

	ntx.(*Tx).mu.Lock()
	defer ntx.(*Tx).mu.Unlock()
	return s.write(datatype, realm, user, id, rev, content, history, ntx.(*Tx).state)
}

func (s *Store) write(datatype, realm, user, id string, rev int64, content proto.Message, history proto.Message, state State) error {
	key := Key{datatype, realm, user, id, Rev(rev)}
	state.Data[key] = proto.Clone(content)
	if rev != storage.LatestRev {
		latest := key
		latest.Rev = Rev(storage.LatestRev)
		state.Data[latest] = proto.Clone(content)
	}
	if history != nil {
		state.History[key] = proto.Clone(history)
	}
	return nil
}

// Delete deletes an item.
func (s *Store) Delete(datatype, realm, user, id string, rev int64) error {
	return s.DeleteTx(datatype, realm, user, id, rev, nil)
}

// DeleteTx deletes an item inside a transaction.
func (s *Store) DeleteTx(datatype, realm, user, id string, rev int64, tx storage.Tx) (ferr error) {
	ntx := tx
	if ntx == nil {
		var err error
		ntx, err = s.Tx(true)
		if err != nil {
			return err
		}
		defer func() {
			err := ntx.Finish()
			if ferr == nil {
				ferr = err
			}
		}()
	}

	ntx.(*Tx).mu.Lock()
	defer ntx.(*Tx).mu.Unlock()
	return s.delete(datatype, realm, user, id, rev, ntx.(*Tx).state)
}

func (s *Store) delete(datatype, realm, user, id string, rev int64, state State) error {
	key := Key{datatype, realm, user, id, Rev(rev)}
	delete(state.Data, key)
	return nil
}

// MultiDeleteTx deletes an item inside a transaction.
func (s *Store) MultiDeleteTx(datatype, realm, user string, tx storage.Tx) (ferr error) {
	ntx := tx
	if ntx == nil {
		var err error
		ntx, err = s.Tx(true)
		if err != nil {
			return err
		}
		defer func() {
			err := ntx.Finish()
			if ferr == nil {
				ferr = err
			}
		}()
	}

	ntx.(*Tx).mu.Lock()
	defer ntx.(*Tx).mu.Unlock()
	return s.multiDelete(datatype, realm, user, ntx.(*Tx).state)
}

func (s *Store) multiDelete(datatype, realm, user string, state State) error {
	for k := range state.Data {
		if k.Datatype == datatype && k.Realm == realm && k.User == user {
			delete(state.Data, k)
		}
	}
	return nil
}

// Wipe clears a realm.
func (s *Store) Wipe(realm string) (ferr error) {
	ntx, err := s.Tx(true)
	if err != nil {
		return err
	}
	defer func() {
		err := ntx.Finish()
		if ferr == nil {
			ferr = err
		}
	}()

	ntx.(*Tx).mu.Lock()
	defer ntx.(*Tx).mu.Unlock()
	return s.wipe(realm, ntx.(*Tx).state)
}

func (s *Store) wipe(realm string, state State) error {
	for k := range state.Data {
		if k.Realm == realm {
			delete(state.Data, k)
		}
	}
	return nil
}

// LockTx creates a lock with the give name.
func (s *Store) LockTx(lockName string, minFrequency time.Duration, tx storage.Tx) storage.Tx {
	// TODO: not sure about the behavior for this one.
	return tx
}

var _ storage.Store = &Store{}
<commit_msg>fix build<commit_after>// Copyright 2019 Google LLC
//
// 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 fakestore provides a fake in-mem storage.
// The implementation is based on the current real implementation using Datastore.
// See /lib/dsstore/
// TODO: once we have a fake Datastore server, get rid of this.
package fakestore

import (
	"fmt"
	"sort"
	"time"

	"github.com/golang/protobuf/proto" /* copybara-comment */
	"google.golang.org/grpc/codes" /* copybara-comment */
	"google.golang.org/grpc/status" /* copybara-comment */
	"github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/storage" /* copybara-comment: storage */
)

// Rev gives the string value of a revision.
func Rev(rev int64) string {
	if rev == storage.LatestRev {
		return storage.LatestRevName
	}
	return fmt.Sprintf("%06d", rev)
}

// Store is a fake in-mem store of data.
type Store struct {
	// Information map.
	Information map[string]string
	// State is where the data resides.
	// Any block of code that wants to access the state reads it from the chan,
	// performs its operations, and then writes it back to the chan.
	// See https://bit.ly/37ANPk4
	State chan State
}

// New creates a new Store.
func New() *Store {
	f := &Store{State: make(chan State, 1)}
	state := State{
		Data:    make(Data),
		History: make(Data),
	}
	f.State <- state
	return f
}

// Tx creates a new transaction.
func (s *Store) Tx(update bool) (storage.Tx, error) {
	return NewTx(s.State, update), nil
}

// Info returns information about the storage.
func (s *Store) Info() map[string]string {
	return s.Information
}

// Exists checks if data item with given key exists.
func (s *Store) Exists(datatype, realm, user, id string, rev int64) (_ bool, ferr error) {
	ntx, err := s.Tx(false)
	if err != nil {
		return false, err
	}
	defer func() {
		err := ntx.Finish()
		if ferr == nil {
			ferr = err
		}
	}()

	ntx.(*Tx).mu.Lock()
	defer ntx.(*Tx).mu.Unlock()
	return s.exists(datatype, realm, user, id, rev, ntx.(*Tx).state)
}

func (s *Store) exists(datatype, realm, user, id string, rev int64, state State) (bool, error) {
	key := Key{datatype, realm, user, id, Rev(rev)}
	if _, ok := state.Data[key]; !ok {
		return false, nil
	}
	return true, nil
}

// Read reads a data item of a given key.
func (s *Store) Read(datatype, realm, user, id string, rev int64, content proto.Message) error {
	return s.ReadTx(datatype, realm, user, id, rev, content, nil)
}

// ReadTx reads a data item of a given key inside a transaction.
// Calls Read if transaction is nil.
func (s *Store) ReadTx(datatype, realm, user, id string, rev int64, content proto.Message, tx storage.Tx) (ferr error) {
	ntx := tx
	if ntx == nil {
		var err error
		ntx, err = s.Tx(false)
		if err != nil {
			return err
		}
		// We need to call finish for this transaction.
		// We need to update the error returned from the function if commit fails.
		defer func() {
			err := ntx.Finish()
			if ferr == nil {
				ferr = err
			}
		}()
	}

	ntx.(*Tx).mu.Lock()
	defer ntx.(*Tx).mu.Unlock()
	return s.read(datatype, realm, user, id, rev, content, ntx.(*Tx).state)
}

func (s *Store) read(datatype, realm, user, id string, rev int64, content proto.Message, state State) error {
	key := Key{datatype, realm, user, id, Rev(rev)}
	v, ok := state.Data[key]
	if !ok {
		return status.Errorf(codes.NotFound, "not found: %+v rev:%v", key, rev)
	}

	content.Reset()
	proto.Merge(content, v)
	return nil
}

// MultiReadTx reads a set of items matching the input parameters and filters.
// Returns total count and error.
//
// content will contain the items which
//   their key matches the provided datatype, realm, user (if realm/user are not "")
//   their value matches the provider filers
// Items are sorted by their key's user and id in ascending order.
// The type of the item's value should be typ.
// Last revision of the items is used.
//
// content's maps are keyed by user and id of the keys.
func (s *Store) MultiReadTx(
	datatype, realm, user string,
	filters [][]storage.Filter,
	offset, pageSize int,
	content map[string]map[string]proto.Message,
	typ proto.Message,
	tx storage.Tx,
) (_ int, ferr error) {
	ntx := tx
	if ntx == nil {
		var err error
		ntx, err = s.Tx(false)
		if err != nil {
			return 0, err
		}
		defer func() {
			err := ntx.Finish()
			if ferr == nil {
				ferr = err
			}
		}()
	}

	ntx.(*Tx).mu.Lock()
	defer ntx.(*Tx).mu.Unlock()
	return s.multiRead(datatype, realm, user, filters, offset, pageSize, content, typ, ntx.(*Tx).state)
}

func (s *Store) multiRead(
	datatype, realm, user string,
	filters [][]storage.Filter,
	offset, pageSize int,
	content map[string]map[string]proto.Message,
	typ proto.Message,
	state State,
) (int, error) {
	if content == nil {
		return 0, status.Error(codes.InvalidArgument, "content cannot be nil")
	}
	if len(content) != 0 {
		return 0, status.Error(codes.InvalidArgument, "content is not empty")
	}

	var res KVList
	for k, v := range state.Data {
		if k.Datatype != datatype {
			continue
		}
		if k.Realm != "" && k.Realm != realm {
			continue
		}
		if k.User != "" && k.User != user {
			continue
		}
		if k.Rev != storage.LatestRevName {
			continue
		}

		if !storage.MatchProtoFilters(filters, v) {
			continue
		}

		// TODO: check the type of v matches the type of typ

		res = append(res, KV{k, v})
	}

	sort.Sort(res)

	i := offset
	for ; i < len(res) && i < offset+pageSize; i++ {
		k := res[i].K
		v := res[i].V
		if _, ok := content[k.User]; !ok {
			content[k.User] = make(map[string]proto.Message)
		}
		content[k.User][k.ID] = proto.Clone(v)
	}

	return i - offset, nil
}

// ReadHistory reads the history of a given key.
func (s *Store) ReadHistory(datatype, realm, user, id string, content *[]proto.Message) error {
	return s.ReadHistoryTx(datatype, realm, user, id, content, nil)
}

// ReadHistoryTx reads the history of a given key inside a transaction.
func (s *Store) ReadHistoryTx(datatype, realm, user, id string, content *[]proto.Message, tx storage.Tx) (ferr error) {
	ntx := tx
	if ntx == nil {
		var err error
		ntx, err = s.Tx(false)
		if err != nil {
			return err
		}
		defer func() {
			err := ntx.Finish()
			if ferr == nil {
				ferr = err
			}
		}()
	}

	ntx.(*Tx).mu.Lock()
	defer ntx.(*Tx).mu.Unlock()
	return s.readHistory(datatype, realm, user, id, content, ntx.(*Tx).state)
}

// readHistory reads the history of a given key inside a transaction.
func (s *Store) readHistory(datatype, realm, user, id string, content *[]proto.Message, state State) error {
	if content == nil {
		return status.Error(codes.NotFound, "content cannot be nil")
	}
	key := Key{datatype, realm, user, id, ""}

	var res []proto.Message
	for k, v := range state.History {
		k.Rev = ""
		if k != key {
			continue
		}
		res = append(res, proto.Clone(v))
	}
	*content = res
	return nil
}

// Write writes an item.
func (s *Store) Write(datatype, realm, user, id string, rev int64, content proto.Message, history proto.Message) error {
	return s.WriteTx(datatype, realm, user, id, rev, content, history, nil)
}

// WriteTx writes an item inside a transaction.
func (s *Store) WriteTx(datatype, realm, user, id string, rev int64, content proto.Message, history proto.Message, tx storage.Tx) (ferr error) {
	ntx := tx
	if ntx == nil {
		var err error
		ntx, err = s.Tx(true)
		if err != nil {
			return err
		}
		defer func() {
			err := ntx.Finish()
			if ferr == nil {
				ferr = err
			}
		}()
	}

	ntx.(*Tx).mu.Lock()
	defer ntx.(*Tx).mu.Unlock()
	return s.write(datatype, realm, user, id, rev, content, history, ntx.(*Tx).state)
}

func (s *Store) write(datatype, realm, user, id string, rev int64, content proto.Message, history proto.Message, state State) error {
	key := Key{datatype, realm, user, id, Rev(rev)}
	state.Data[key] = proto.Clone(content)
	if rev != storage.LatestRev {
		latest := key
		latest.Rev = Rev(storage.LatestRev)
		state.Data[latest] = proto.Clone(content)
	}
	if history != nil {
		state.History[key] = proto.Clone(history)
	}
	return nil
}

// Delete deletes an item.
func (s *Store) Delete(datatype, realm, user, id string, rev int64) error {
	return s.DeleteTx(datatype, realm, user, id, rev, nil)
}

// DeleteTx deletes an item inside a transaction.
func (s *Store) DeleteTx(datatype, realm, user, id string, rev int64, tx storage.Tx) (ferr error) {
	ntx := tx
	if ntx == nil {
		var err error
		ntx, err = s.Tx(true)
		if err != nil {
			return err
		}
		defer func() {
			err := ntx.Finish()
			if ferr == nil {
				ferr = err
			}
		}()
	}

	ntx.(*Tx).mu.Lock()
	defer ntx.(*Tx).mu.Unlock()
	return s.delete(datatype, realm, user, id, rev, ntx.(*Tx).state)
}

func (s *Store) delete(datatype, realm, user, id string, rev int64, state State) error {
	key := Key{datatype, realm, user, id, Rev(rev)}
	delete(state.Data, key)
	return nil
}

// MultiDeleteTx deletes an item inside a transaction.
func (s *Store) MultiDeleteTx(datatype, realm, user string, tx storage.Tx) (ferr error) {
	ntx := tx
	if ntx == nil {
		var err error
		ntx, err = s.Tx(true)
		if err != nil {
			return err
		}
		defer func() {
			err := ntx.Finish()
			if ferr == nil {
				ferr = err
			}
		}()
	}

	ntx.(*Tx).mu.Lock()
	defer ntx.(*Tx).mu.Unlock()
	return s.multiDelete(datatype, realm, user, ntx.(*Tx).state)
}

func (s *Store) multiDelete(datatype, realm, user string, state State) error {
	for k := range state.Data {
		if k.Datatype == datatype && k.Realm == realm && k.User == user {
			delete(state.Data, k)
		}
	}
	return nil
}

// Wipe clears a realm.
func (s *Store) Wipe(realm string) (ferr error) {
	ntx, err := s.Tx(true)
	if err != nil {
		return err
	}
	defer func() {
		err := ntx.Finish()
		if ferr == nil {
			ferr = err
		}
	}()

	ntx.(*Tx).mu.Lock()
	defer ntx.(*Tx).mu.Unlock()
	return s.wipe(realm, ntx.(*Tx).state)
}

func (s *Store) wipe(realm string, state State) error {
	for k := range state.Data {
		if k.Realm == realm {
			delete(state.Data, k)
		}
	}
	return nil
}

// LockTx creates a lock with the give name.
func (s *Store) LockTx(lockName string, minFrequency time.Duration, tx storage.Tx) storage.Tx {
	// TODO: not sure about the behavior for this one.
	return tx
}

var _ storage.Store = &Store{}
<|endoftext|>


<commit_before>package llbsolver

import (
	"context"
	"fmt"
	"io"
	"strings"
	"sync"
	"time"

	"github.com/containerd/containerd/platforms"
	"github.com/moby/buildkit/cache"
	"github.com/moby/buildkit/cache/remotecache"
	"github.com/moby/buildkit/client/llb"
	"github.com/moby/buildkit/executor"
	"github.com/moby/buildkit/frontend"
	gw "github.com/moby/buildkit/frontend/gateway/client"
	"github.com/moby/buildkit/identity"
	"github.com/moby/buildkit/session"
	"github.com/moby/buildkit/solver"
	"github.com/moby/buildkit/solver/pb"
	"github.com/moby/buildkit/util/flightcontrol"
	"github.com/moby/buildkit/util/tracing"
	"github.com/moby/buildkit/worker"
	digest "github.com/opencontainers/go-digest"
	specs "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

type llbBridge struct {
	builder                   solver.Builder
	frontends                 map[string]frontend.Frontend
	resolveWorker             func() (worker.Worker, error)
	eachWorker                func(func(worker.Worker) error) error
	resolveCacheImporterFuncs map[string]remotecache.ResolveCacheImporterFunc
	cms                       map[string]solver.CacheManager
	cmsMu                     sync.Mutex
	platforms                 []specs.Platform
	sm                        *session.Manager
}

func (b *llbBridge) Solve(ctx context.Context, req frontend.SolveRequest) (res *frontend.Result, err error) {
	w, err := b.resolveWorker()
	if err != nil {
		return nil, err
	}
	var cms []solver.CacheManager
	for _, im := range req.CacheImports {
		b.cmsMu.Lock()
		var cm solver.CacheManager
		cmId := identity.NewID()
		if im.Type == "registry" {
			// For compatibility with < v0.4.0
			if ref := im.Attrs["ref"]; ref != "" {
				cmId = ref
			}
		}
		if prevCm, ok := b.cms[cmId]; !ok {
			func(cmId string, im gw.CacheOptionsEntry) {
				cm = newLazyCacheManager(cmId, func() (solver.CacheManager, error) {
					var cmNew solver.CacheManager
					if err := inVertexContext(b.builder.Context(context.TODO()), "importing cache manifest from "+cmId, "", func(ctx context.Context) error {
						resolveCI, ok := b.resolveCacheImporterFuncs[im.Type]
						if !ok {
							return errors.Errorf("unknown cache importer: %s", im.Type)
						}
						ci, desc, err := resolveCI(ctx, im.Attrs)
						if err != nil {
							return err
						}
						cmNew, err = ci.Resolve(ctx, desc, cmId, w)
						return err
					}); err != nil {
						logrus.Debugf("error while importing cache manifest from cmId=%s: %v", cmId, err)
						return nil, err
					}
					return cmNew, nil
				})
			}(cmId, im)
			b.cms[cmId] = cm
		} else {
			cm = prevCm
		}
		cms = append(cms, cm)
		b.cmsMu.Unlock()
	}

	if req.Definition != nil && req.Definition.Def != nil && req.Frontend != "" {
		return nil, errors.New("cannot solve with both Definition and Frontend specified")
	}

	if req.Definition != nil && req.Definition.Def != nil {
		ent, err := loadEntitlements(b.builder)
		if err != nil {
			return nil, err
		}
		dpc := &detectPrunedCacheID{}

		edge, err := Load(req.Definition, dpc.Load, ValidateEntitlements(ent), WithCacheSources(cms), RuntimePlatforms(b.platforms), WithValidateCaps())
		if err != nil {
			return nil, errors.Wrap(err, "failed to load LLB")
		}

		if len(dpc.ids) > 0 {
			ids := make([]string, 0, len(dpc.ids))
			for id := range dpc.ids {
				ids = append(ids, id)
			}
			if err := b.eachWorker(func(w worker.Worker) error {
				return w.PruneCacheMounts(ctx, ids)
			}); err != nil {
				return nil, err
			}
		}

		res = &frontend.Result{
			Ref: &resultProxy{
				def: req.Definition,
				cb: func(ctx context.Context) (solver.CachedResult, error) {
					res, err := b.builder.Build(ctx, edge)
					if err != nil {
						return nil, err
					}
					wr, ok := res.Sys().(*worker.WorkerRef)
					if !ok {
						return nil, errors.Errorf("invalid reference for exporting: %T", res.Sys())
					}
					if wr.ImmutableRef != nil {
						if err := wr.ImmutableRef.Finalize(ctx, false); err != nil {
							return nil, err
						}
					}
					return res, err
				},
			},
		}
	} else if req.Frontend != "" {
		f, ok := b.frontends[req.Frontend]
		if !ok {
			return nil, errors.Errorf("invalid frontend: %s", req.Frontend)
		}
		res, err = f.Solve(ctx, b, req.FrontendOpt)
		if err != nil {
			return nil, errors.Wrapf(err, "failed to solve with frontend %s", req.Frontend)
		}
	} else {
		return &frontend.Result{}, nil
	}

	return
}

type resultProxy struct {
	cb       func(context.Context) (solver.CachedResult, error)
	def      *pb.Definition
	g        flightcontrol.Group
	mu       sync.Mutex
	released bool
	v        solver.CachedResult
	err      error
}

func (rp *resultProxy) Definition() *pb.Definition {
	return rp.def
}

func (rp *resultProxy) Release(ctx context.Context) error {
	rp.mu.Lock()
	defer rp.mu.Unlock()
	if rp.v != nil {
		if err := rp.v.Release(ctx); err != nil {
			return err
		}
	}
	rp.released = true
	return nil
}

func (rp *resultProxy) Result(ctx context.Context) (solver.CachedResult, error) {
	r, err := rp.g.Do(ctx, "result", func(ctx context.Context) (interface{}, error) {
		rp.mu.Lock()
		if rp.released {
			rp.mu.Unlock()
			return nil, errors.Errorf("accessing released result")
		}
		if rp.v != nil || rp.err != nil {
			rp.mu.Unlock()
			return rp.v, rp.err
		}
		rp.mu.Unlock()
		v, err := rp.cb(ctx)
		if err != nil {
			select {
			case <-ctx.Done():
				if strings.Contains(err.Error(), context.Canceled.Error()) {
					return v, err
				}
			default:
			}
		}
		rp.mu.Lock()
		if rp.released {
			if v != nil {
				v.Release(context.TODO())
			}
			rp.mu.Unlock()
			return nil, errors.Errorf("evaluating released result")
		}
		rp.v = v
		rp.err = err
		rp.mu.Unlock()
		return v, err
	})
	if r != nil {
		return r.(solver.CachedResult), nil
	}
	return nil, err
}

func (s *llbBridge) Exec(ctx context.Context, meta executor.Meta, root cache.ImmutableRef, stdin io.ReadCloser, stdout, stderr io.WriteCloser) (err error) {
	w, err := s.resolveWorker()
	if err != nil {
		return err
	}
	span, ctx := tracing.StartSpan(ctx, strings.Join(meta.Args, " "))
	err = w.Exec(ctx, meta, root, stdin, stdout, stderr)
	tracing.FinishWithError(span, err)
	return err
}

func (s *llbBridge) ResolveImageConfig(ctx context.Context, ref string, opt llb.ResolveImageConfigOpt) (dgst digest.Digest, config []byte, err error) {
	w, err := s.resolveWorker()
	if err != nil {
		return "", nil, err
	}
	if opt.LogName == "" {
		opt.LogName = fmt.Sprintf("resolve image config for %s", ref)
	}
	id := ref // make a deterministic ID for avoiding duplicates
	if platform := opt.Platform; platform == nil {
		id += platforms.Format(platforms.DefaultSpec())
	} else {
		id += platforms.Format(*platform)
	}
	err = inVertexContext(s.builder.Context(ctx), opt.LogName, id, func(ctx context.Context) error {
		dgst, config, err = w.ResolveImageConfig(ctx, ref, opt, s.sm)
		return err
	})
	return dgst, config, err
}

type lazyCacheManager struct {
	id   string
	main solver.CacheManager

	waitCh chan struct{}
	err    error
}

func (lcm *lazyCacheManager) ID() string {
	return lcm.id
}
func (lcm *lazyCacheManager) Query(inp []solver.CacheKeyWithSelector, inputIndex solver.Index, dgst digest.Digest, outputIndex solver.Index) ([]*solver.CacheKey, error) {
	lcm.wait()
	if lcm.main == nil {
		return nil, nil
	}
	return lcm.main.Query(inp, inputIndex, dgst, outputIndex)
}
func (lcm *lazyCacheManager) Records(ck *solver.CacheKey) ([]*solver.CacheRecord, error) {
	lcm.wait()
	if lcm.main == nil {
		return nil, nil
	}
	return lcm.main.Records(ck)
}
func (lcm *lazyCacheManager) Load(ctx context.Context, rec *solver.CacheRecord) (solver.Result, error) {
	if err := lcm.wait(); err != nil {
		return nil, err
	}
	return lcm.main.Load(ctx, rec)
}
func (lcm *lazyCacheManager) Save(key *solver.CacheKey, s solver.Result, createdAt time.Time) (*solver.ExportableCacheKey, error) {
	if err := lcm.wait(); err != nil {
		return nil, err
	}
	return lcm.main.Save(key, s, createdAt)
}

func (lcm *lazyCacheManager) wait() error {
	<-lcm.waitCh
	return lcm.err
}

func newLazyCacheManager(id string, fn func() (solver.CacheManager, error)) solver.CacheManager {
	lcm := &lazyCacheManager{id: id, waitCh: make(chan struct{})}
	go func() {
		defer close(lcm.waitCh)
		cm, err := fn()
		if err != nil {
			lcm.err = err
			return
		}
		lcm.main = cm
	}()
	return lcm
}
<commit_msg>solver: use correct context for getting cache managers lazily<commit_after>package llbsolver

import (
	"context"
	"fmt"
	"io"
	"strings"
	"sync"
	"time"

	"github.com/containerd/containerd/platforms"
	"github.com/mitchellh/hashstructure"
	"github.com/moby/buildkit/cache"
	"github.com/moby/buildkit/cache/remotecache"
	"github.com/moby/buildkit/client/llb"
	"github.com/moby/buildkit/executor"
	"github.com/moby/buildkit/frontend"
	gw "github.com/moby/buildkit/frontend/gateway/client"
	"github.com/moby/buildkit/session"
	"github.com/moby/buildkit/solver"
	"github.com/moby/buildkit/solver/pb"
	"github.com/moby/buildkit/util/flightcontrol"
	"github.com/moby/buildkit/util/tracing"
	"github.com/moby/buildkit/worker"
	digest "github.com/opencontainers/go-digest"
	specs "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

type llbBridge struct {
	builder                   solver.Builder
	frontends                 map[string]frontend.Frontend
	resolveWorker             func() (worker.Worker, error)
	eachWorker                func(func(worker.Worker) error) error
	resolveCacheImporterFuncs map[string]remotecache.ResolveCacheImporterFunc
	cms                       map[string]solver.CacheManager
	cmsMu                     sync.Mutex
	platforms                 []specs.Platform
	sm                        *session.Manager
}

func (b *llbBridge) loadResult(ctx context.Context, def *pb.Definition, cacheImports []gw.CacheOptionsEntry) (solver.CachedResult, error) {
	w, err := b.resolveWorker()
	if err != nil {
		return nil, err
	}
	ent, err := loadEntitlements(b.builder)
	if err != nil {
		return nil, err
	}
	var cms []solver.CacheManager
	for _, im := range cacheImports {
		cmID, err := cmKey(im)
		if err != nil {
			return nil, err
		}
		b.cmsMu.Lock()
		var cm solver.CacheManager
		if prevCm, ok := b.cms[cmID]; !ok {
			func(cmID string, im gw.CacheOptionsEntry) {
				cm = newLazyCacheManager(cmID, func() (solver.CacheManager, error) {
					var cmNew solver.CacheManager
					if err := inVertexContext(b.builder.Context(context.TODO()), "importing cache manifest from "+cmID, "", func(ctx context.Context) error {
						resolveCI, ok := b.resolveCacheImporterFuncs[im.Type]
						if !ok {
							return errors.Errorf("unknown cache importer: %s", im.Type)
						}
						ci, desc, err := resolveCI(ctx, im.Attrs)
						if err != nil {
							return err
						}
						cmNew, err = ci.Resolve(ctx, desc, cmID, w)
						return err
					}); err != nil {
						logrus.Debugf("error while importing cache manifest from cmId=%s: %v", cmID, err)
						return nil, err
					}
					return cmNew, nil
				})
			}(cmID, im)
			b.cms[cmID] = cm
		} else {
			cm = prevCm
		}
		cms = append(cms, cm)
		b.cmsMu.Unlock()
	}
	dpc := &detectPrunedCacheID{}

	edge, err := Load(def, dpc.Load, ValidateEntitlements(ent), WithCacheSources(cms), RuntimePlatforms(b.platforms), WithValidateCaps())
	if err != nil {
		return nil, errors.Wrap(err, "failed to load LLB")
	}

	if len(dpc.ids) > 0 {
		ids := make([]string, 0, len(dpc.ids))
		for id := range dpc.ids {
			ids = append(ids, id)
		}
		if err := b.eachWorker(func(w worker.Worker) error {
			return w.PruneCacheMounts(ctx, ids)
		}); err != nil {
			return nil, err
		}
	}

	res, err := b.builder.Build(ctx, edge)
	if err != nil {
		return nil, err
	}
	wr, ok := res.Sys().(*worker.WorkerRef)
	if !ok {
		return nil, errors.Errorf("invalid reference for exporting: %T", res.Sys())
	}
	if wr.ImmutableRef != nil {
		if err := wr.ImmutableRef.Finalize(ctx, false); err != nil {
			return nil, err
		}
	}
	return res, err
}

func (b *llbBridge) Solve(ctx context.Context, req frontend.SolveRequest) (res *frontend.Result, err error) {
	if req.Definition != nil && req.Definition.Def != nil && req.Frontend != "" {
		return nil, errors.New("cannot solve with both Definition and Frontend specified")
	}

	if req.Definition != nil && req.Definition.Def != nil {
		res = &frontend.Result{Ref: newResultProxy(b, req)}
	} else if req.Frontend != "" {
		f, ok := b.frontends[req.Frontend]
		if !ok {
			return nil, errors.Errorf("invalid frontend: %s", req.Frontend)
		}
		res, err = f.Solve(ctx, b, req.FrontendOpt)
		if err != nil {
			return nil, errors.Wrapf(err, "failed to solve with frontend %s", req.Frontend)
		}
	} else {
		return &frontend.Result{}, nil
	}

	return
}

type resultProxy struct {
	cb       func(context.Context) (solver.CachedResult, error)
	def      *pb.Definition
	g        flightcontrol.Group
	mu       sync.Mutex
	released bool
	v        solver.CachedResult
	err      error
}

func newResultProxy(b *llbBridge, req frontend.SolveRequest) *resultProxy {
	return &resultProxy{
		def: req.Definition,
		cb: func(ctx context.Context) (solver.CachedResult, error) {
			return b.loadResult(ctx, req.Definition, req.CacheImports)
		},
	}
}

func (rp *resultProxy) Definition() *pb.Definition {
	return rp.def
}

func (rp *resultProxy) Release(ctx context.Context) error {
	rp.mu.Lock()
	defer rp.mu.Unlock()
	if rp.v != nil {
		if rp.released {
			logrus.Warnf("release of already released result")
		}
		if err := rp.v.Release(ctx); err != nil {
			return err
		}
	}
	rp.released = true
	return nil
}

func (rp *resultProxy) Result(ctx context.Context) (solver.CachedResult, error) {
	r, err := rp.g.Do(ctx, "result", func(ctx context.Context) (interface{}, error) {
		rp.mu.Lock()
		if rp.released {
			rp.mu.Unlock()
			return nil, errors.Errorf("accessing released result")
		}
		if rp.v != nil || rp.err != nil {
			rp.mu.Unlock()
			return rp.v, rp.err
		}
		rp.mu.Unlock()
		v, err := rp.cb(ctx)
		if err != nil {
			select {
			case <-ctx.Done():
				if strings.Contains(err.Error(), context.Canceled.Error()) {
					return v, err
				}
			default:
			}
		}
		rp.mu.Lock()
		if rp.released {
			if v != nil {
				v.Release(context.TODO())
			}
			rp.mu.Unlock()
			return nil, errors.Errorf("evaluating released result")
		}
		rp.v = v
		rp.err = err
		rp.mu.Unlock()
		return v, err
	})
	if r != nil {
		return r.(solver.CachedResult), nil
	}
	return nil, err
}

func (s *llbBridge) Exec(ctx context.Context, meta executor.Meta, root cache.ImmutableRef, stdin io.ReadCloser, stdout, stderr io.WriteCloser) (err error) {
	w, err := s.resolveWorker()
	if err != nil {
		return err
	}
	span, ctx := tracing.StartSpan(ctx, strings.Join(meta.Args, " "))
	err = w.Exec(ctx, meta, root, stdin, stdout, stderr)
	tracing.FinishWithError(span, err)
	return err
}

func (s *llbBridge) ResolveImageConfig(ctx context.Context, ref string, opt llb.ResolveImageConfigOpt) (dgst digest.Digest, config []byte, err error) {
	w, err := s.resolveWorker()
	if err != nil {
		return "", nil, err
	}
	if opt.LogName == "" {
		opt.LogName = fmt.Sprintf("resolve image config for %s", ref)
	}
	id := ref // make a deterministic ID for avoiding duplicates
	if platform := opt.Platform; platform == nil {
		id += platforms.Format(platforms.DefaultSpec())
	} else {
		id += platforms.Format(*platform)
	}
	err = inVertexContext(s.builder.Context(ctx), opt.LogName, id, func(ctx context.Context) error {
		dgst, config, err = w.ResolveImageConfig(ctx, ref, opt, s.sm)
		return err
	})
	return dgst, config, err
}

type lazyCacheManager struct {
	id   string
	main solver.CacheManager

	waitCh chan struct{}
	err    error
}

func (lcm *lazyCacheManager) ID() string {
	return lcm.id
}
func (lcm *lazyCacheManager) Query(inp []solver.CacheKeyWithSelector, inputIndex solver.Index, dgst digest.Digest, outputIndex solver.Index) ([]*solver.CacheKey, error) {
	lcm.wait()
	if lcm.main == nil {
		return nil, nil
	}
	return lcm.main.Query(inp, inputIndex, dgst, outputIndex)
}
func (lcm *lazyCacheManager) Records(ck *solver.CacheKey) ([]*solver.CacheRecord, error) {
	lcm.wait()
	if lcm.main == nil {
		return nil, nil
	}
	return lcm.main.Records(ck)
}
func (lcm *lazyCacheManager) Load(ctx context.Context, rec *solver.CacheRecord) (solver.Result, error) {
	if err := lcm.wait(); err != nil {
		return nil, err
	}
	return lcm.main.Load(ctx, rec)
}
func (lcm *lazyCacheManager) Save(key *solver.CacheKey, s solver.Result, createdAt time.Time) (*solver.ExportableCacheKey, error) {
	if err := lcm.wait(); err != nil {
		return nil, err
	}
	return lcm.main.Save(key, s, createdAt)
}

func (lcm *lazyCacheManager) wait() error {
	<-lcm.waitCh
	return lcm.err
}

func newLazyCacheManager(id string, fn func() (solver.CacheManager, error)) solver.CacheManager {
	lcm := &lazyCacheManager{id: id, waitCh: make(chan struct{})}
	go func() {
		defer close(lcm.waitCh)
		cm, err := fn()
		if err != nil {
			lcm.err = err
			return
		}
		lcm.main = cm
	}()
	return lcm
}

func cmKey(im gw.CacheOptionsEntry) (string, error) {
	if im.Type == "registry" && im.Attrs["ref"] != "" {
		return im.Attrs["ref"], nil
	}
	i, err := hashstructure.Hash(im, nil)
	if err != nil {
		return "", err
	}
	return fmt.Sprintf("%s:%d", im.Type, i), nil
}
<|endoftext|>


<commit_before>// +build linux

package fs2

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/opencontainers/runc/libcontainer/cgroups"
	"github.com/opencontainers/runc/libcontainer/configs"
	"github.com/pkg/errors"
)

type manager struct {
	config *configs.Cgroup
	// dirPath is like "/sys/fs/cgroup/user.slice/user-1001.slice/session-1.scope"
	dirPath string
	// controllers is content of "cgroup.controllers" file.
	// excludes pseudo-controllers ("devices" and "freezer").
	controllers map[string]struct{}
	rootless    bool
}

// NewManager creates a manager for cgroup v2 unified hierarchy.
// dirPath is like "/sys/fs/cgroup/user.slice/user-1001.slice/session-1.scope".
// If dirPath is empty, it is automatically set using config.
func NewManager(config *configs.Cgroup, dirPath string, rootless bool) (cgroups.Manager, error) {
	if config == nil {
		config = &configs.Cgroup{}
	}
	if dirPath == "" {
		var err error
		dirPath, err = defaultDirPath(config)
		if err != nil {
			return nil, err
		}
	}

	m := &manager{
		config:   config,
		dirPath:  dirPath,
		rootless: rootless,
	}
	return m, nil
}

func (m *manager) getControllers() error {
	if m.controllers != nil {
		return nil
	}

	file := filepath.Join(m.dirPath, "cgroup.controllers")
	data, err := ioutil.ReadFile(file)
	if err != nil {
		if m.rootless && m.config.Path == "" {
			return nil
		}
		return err
	}
	fields := strings.Fields(string(data))
	m.controllers = make(map[string]struct{}, len(fields))
	for _, c := range fields {
		m.controllers[c] = struct{}{}
	}

	return nil
}

func (m *manager) Apply(pid int) error {
	if err := CreateCgroupPath(m.dirPath, m.config); err != nil {
		// Related tests:
		// - "runc create (no limits + no cgrouppath + no permission) succeeds"
		// - "runc create (rootless + no limits + cgrouppath + no permission) fails with permission error"
		// - "runc create (rootless + limits + no cgrouppath + no permission) fails with informative error"
		if m.rootless {
			if m.config.Path == "" {
				if blNeed, nErr := needAnyControllers(m.config); nErr == nil && !blNeed {
					return nil
				}
				return errors.Wrap(err, "rootless needs no limits + no cgrouppath when no permission is granted for cgroups")
			}
		}
		return err
	}
	if err := cgroups.WriteCgroupProc(m.dirPath, pid); err != nil {
		return err
	}
	return nil
}

func (m *manager) GetPids() ([]int, error) {
	return cgroups.GetPids(m.dirPath)
}

func (m *manager) GetAllPids() ([]int, error) {
	return cgroups.GetAllPids(m.dirPath)
}

func (m *manager) GetStats() (*cgroups.Stats, error) {
	var (
		errs []error
	)

	st := cgroups.NewStats()
	if err := m.getControllers(); err != nil {
		return st, err
	}

	// pids (since kernel 4.5)
	if _, ok := m.controllers["pids"]; ok {
		if err := statPids(m.dirPath, st); err != nil {
			errs = append(errs, err)
		}
	} else {
		if err := statPidsWithoutController(m.dirPath, st); err != nil {
			errs = append(errs, err)
		}
	}
	// memory (since kernel 4.5)
	if _, ok := m.controllers["memory"]; ok {
		if err := statMemory(m.dirPath, st); err != nil {
			errs = append(errs, err)
		}
	}
	// io (since kernel 4.5)
	if _, ok := m.controllers["io"]; ok {
		if err := statIo(m.dirPath, st); err != nil {
			errs = append(errs, err)
		}
	}
	// cpu (since kernel 4.15)
	if _, ok := m.controllers["cpu"]; ok {
		if err := statCpu(m.dirPath, st); err != nil {
			errs = append(errs, err)
		}
	}
	// hugetlb (since kernel 5.6)
	if _, ok := m.controllers["hugetlb"]; ok {
		if err := statHugeTlb(m.dirPath, st); err != nil {
			errs = append(errs, err)
		}
	}
	if len(errs) > 0 && !m.rootless {
		return st, errors.Errorf("error while statting cgroup v2: %+v", errs)
	}
	return st, nil
}

func (m *manager) Freeze(state configs.FreezerState) error {
	if err := setFreezer(m.dirPath, state); err != nil {
		return err
	}
	m.config.Resources.Freezer = state
	return nil
}

// removeCgroupPath aims to remove cgroup path recursively
// Because there may be subcgroups in it.
func removeCgroupPath(path string) error {
	infos, err := ioutil.ReadDir(path)
	if err != nil {
		if os.IsNotExist(err) {
			err = nil
		}
		return err
	}
	for _, info := range infos {
		if info.IsDir() {
			// We should remove subcgroups dir first
			if err = removeCgroupPath(filepath.Join(path, info.Name())); err != nil {
				break
			}
		}
	}
	if err == nil {
		err = os.Remove(path)
		if os.IsNotExist(err) {
			err = nil
		}
	}
	return err
}

func (m *manager) Destroy() error {
	return removeCgroupPath(m.dirPath)
}

func (m *manager) Path(_ string) string {
	return m.dirPath
}

func (m *manager) Set(container *configs.Config) error {
	if container == nil || container.Cgroups == nil {
		return nil
	}
	if err := m.getControllers(); err != nil {
		return err
	}
	// pids (since kernel 4.5)
	if err := setPids(m.dirPath, container.Cgroups); err != nil {
		return err
	}
	// memory (since kernel 4.5)
	if err := setMemory(m.dirPath, container.Cgroups); err != nil {
		return err
	}
	// io (since kernel 4.5)
	if err := setIo(m.dirPath, container.Cgroups); err != nil {
		return err
	}
	// cpu (since kernel 4.15)
	if err := setCpu(m.dirPath, container.Cgroups); err != nil {
		return err
	}
	// devices (since kernel 4.15, pseudo-controller)
	//
	// When m.Rootless is true, errors from the device subsystem are ignored because it is really not expected to work.
	// However, errors from other subsystems are not ignored.
	// see @test "runc create (rootless + limits + no cgrouppath + no permission) fails with informative error"
	if err := setDevices(m.dirPath, container.Cgroups); err != nil && !m.rootless {
		return err
	}
	// cpuset (since kernel 5.0)
	if err := setCpuset(m.dirPath, container.Cgroups); err != nil {
		return err
	}
	// hugetlb (since kernel 5.6)
	if err := setHugeTlb(m.dirPath, container.Cgroups); err != nil {
		return err
	}
	// freezer (since kernel 5.2, pseudo-controller)
	if err := setFreezer(m.dirPath, container.Cgroups.Freezer); err != nil {
		return err
	}
	m.config = container.Cgroups
	return nil
}

func (m *manager) GetPaths() map[string]string {
	paths := make(map[string]string, 1)
	paths[""] = m.dirPath
	return paths
}

func (m *manager) GetCgroups() (*configs.Cgroup, error) {
	return m.config, nil
}

func (m *manager) GetFreezerState() (configs.FreezerState, error) {
	return getFreezer(m.dirPath)
}
<commit_msg>cgroups/fs2: make removeCgroupPath faster<commit_after>// +build linux

package fs2

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/opencontainers/runc/libcontainer/cgroups"
	"github.com/opencontainers/runc/libcontainer/configs"
	"github.com/pkg/errors"
	"golang.org/x/sys/unix"
)

type manager struct {
	config *configs.Cgroup
	// dirPath is like "/sys/fs/cgroup/user.slice/user-1001.slice/session-1.scope"
	dirPath string
	// controllers is content of "cgroup.controllers" file.
	// excludes pseudo-controllers ("devices" and "freezer").
	controllers map[string]struct{}
	rootless    bool
}

// NewManager creates a manager for cgroup v2 unified hierarchy.
// dirPath is like "/sys/fs/cgroup/user.slice/user-1001.slice/session-1.scope".
// If dirPath is empty, it is automatically set using config.
func NewManager(config *configs.Cgroup, dirPath string, rootless bool) (cgroups.Manager, error) {
	if config == nil {
		config = &configs.Cgroup{}
	}
	if dirPath == "" {
		var err error
		dirPath, err = defaultDirPath(config)
		if err != nil {
			return nil, err
		}
	}

	m := &manager{
		config:   config,
		dirPath:  dirPath,
		rootless: rootless,
	}
	return m, nil
}

func (m *manager) getControllers() error {
	if m.controllers != nil {
		return nil
	}

	file := filepath.Join(m.dirPath, "cgroup.controllers")
	data, err := ioutil.ReadFile(file)
	if err != nil {
		if m.rootless && m.config.Path == "" {
			return nil
		}
		return err
	}
	fields := strings.Fields(string(data))
	m.controllers = make(map[string]struct{}, len(fields))
	for _, c := range fields {
		m.controllers[c] = struct{}{}
	}

	return nil
}

func (m *manager) Apply(pid int) error {
	if err := CreateCgroupPath(m.dirPath, m.config); err != nil {
		// Related tests:
		// - "runc create (no limits + no cgrouppath + no permission) succeeds"
		// - "runc create (rootless + no limits + cgrouppath + no permission) fails with permission error"
		// - "runc create (rootless + limits + no cgrouppath + no permission) fails with informative error"
		if m.rootless {
			if m.config.Path == "" {
				if blNeed, nErr := needAnyControllers(m.config); nErr == nil && !blNeed {
					return nil
				}
				return errors.Wrap(err, "rootless needs no limits + no cgrouppath when no permission is granted for cgroups")
			}
		}
		return err
	}
	if err := cgroups.WriteCgroupProc(m.dirPath, pid); err != nil {
		return err
	}
	return nil
}

func (m *manager) GetPids() ([]int, error) {
	return cgroups.GetPids(m.dirPath)
}

func (m *manager) GetAllPids() ([]int, error) {
	return cgroups.GetAllPids(m.dirPath)
}

func (m *manager) GetStats() (*cgroups.Stats, error) {
	var (
		errs []error
	)

	st := cgroups.NewStats()
	if err := m.getControllers(); err != nil {
		return st, err
	}

	// pids (since kernel 4.5)
	if _, ok := m.controllers["pids"]; ok {
		if err := statPids(m.dirPath, st); err != nil {
			errs = append(errs, err)
		}
	} else {
		if err := statPidsWithoutController(m.dirPath, st); err != nil {
			errs = append(errs, err)
		}
	}
	// memory (since kernel 4.5)
	if _, ok := m.controllers["memory"]; ok {
		if err := statMemory(m.dirPath, st); err != nil {
			errs = append(errs, err)
		}
	}
	// io (since kernel 4.5)
	if _, ok := m.controllers["io"]; ok {
		if err := statIo(m.dirPath, st); err != nil {
			errs = append(errs, err)
		}
	}
	// cpu (since kernel 4.15)
	if _, ok := m.controllers["cpu"]; ok {
		if err := statCpu(m.dirPath, st); err != nil {
			errs = append(errs, err)
		}
	}
	// hugetlb (since kernel 5.6)
	if _, ok := m.controllers["hugetlb"]; ok {
		if err := statHugeTlb(m.dirPath, st); err != nil {
			errs = append(errs, err)
		}
	}
	if len(errs) > 0 && !m.rootless {
		return st, errors.Errorf("error while statting cgroup v2: %+v", errs)
	}
	return st, nil
}

func (m *manager) Freeze(state configs.FreezerState) error {
	if err := setFreezer(m.dirPath, state); err != nil {
		return err
	}
	m.config.Resources.Freezer = state
	return nil
}

func rmdir(path string) error {
	err := unix.Rmdir(path)
	if err == nil || err == unix.ENOENT {
		return nil
	}
	return &os.PathError{Op: "rmdir", Path: path, Err: err}
}

// removeCgroupPath aims to remove cgroup path recursively
// Because there may be subcgroups in it.
func removeCgroupPath(path string) error {
	// try the fast path first
	if err := rmdir(path); err == nil {
		return nil
	}

	infos, err := ioutil.ReadDir(path)
	if err != nil {
		if os.IsNotExist(err) {
			err = nil
		}
		return err
	}
	for _, info := range infos {
		if info.IsDir() {
			// We should remove subcgroups dir first
			if err = removeCgroupPath(filepath.Join(path, info.Name())); err != nil {
				break
			}
		}
	}
	if err == nil {
		err = rmdir(path)
	}
	return err
}

func (m *manager) Destroy() error {
	return removeCgroupPath(m.dirPath)
}

func (m *manager) Path(_ string) string {
	return m.dirPath
}

func (m *manager) Set(container *configs.Config) error {
	if container == nil || container.Cgroups == nil {
		return nil
	}
	if err := m.getControllers(); err != nil {
		return err
	}
	// pids (since kernel 4.5)
	if err := setPids(m.dirPath, container.Cgroups); err != nil {
		return err
	}
	// memory (since kernel 4.5)
	if err := setMemory(m.dirPath, container.Cgroups); err != nil {
		return err
	}
	// io (since kernel 4.5)
	if err := setIo(m.dirPath, container.Cgroups); err != nil {
		return err
	}
	// cpu (since kernel 4.15)
	if err := setCpu(m.dirPath, container.Cgroups); err != nil {
		return err
	}
	// devices (since kernel 4.15, pseudo-controller)
	//
	// When m.Rootless is true, errors from the device subsystem are ignored because it is really not expected to work.
	// However, errors from other subsystems are not ignored.
	// see @test "runc create (rootless + limits + no cgrouppath + no permission) fails with informative error"
	if err := setDevices(m.dirPath, container.Cgroups); err != nil && !m.rootless {
		return err
	}
	// cpuset (since kernel 5.0)
	if err := setCpuset(m.dirPath, container.Cgroups); err != nil {
		return err
	}
	// hugetlb (since kernel 5.6)
	if err := setHugeTlb(m.dirPath, container.Cgroups); err != nil {
		return err
	}
	// freezer (since kernel 5.2, pseudo-controller)
	if err := setFreezer(m.dirPath, container.Cgroups.Freezer); err != nil {
		return err
	}
	m.config = container.Cgroups
	return nil
}

func (m *manager) GetPaths() map[string]string {
	paths := make(map[string]string, 1)
	paths[""] = m.dirPath
	return paths
}

func (m *manager) GetCgroups() (*configs.Cgroup, error) {
	return m.config, nil
}

func (m *manager) GetFreezerState() (configs.FreezerState, error) {
	return getFreezer(m.dirPath)
}
<|endoftext|>