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

package state

import (
	"fmt"

	"github.com/juju/errors"
	"labix.org/v2/mgo/bson"
	"labix.org/v2/mgo/txn"
)

type cleanupKind string

const (
	// SCHEMACHANGE: the names are expressive, the values not so much.
	cleanupRelationSettings            cleanupKind = "settings"
	cleanupUnitsForDyingService        cleanupKind = "units"
	cleanupDyingUnit                   cleanupKind = "dyingUnit"
	cleanupRemovedUnit                 cleanupKind = "removedUnit"
	cleanupServicesForDyingEnvironment cleanupKind = "services"
	cleanupForceDestroyedMachine       cleanupKind = "machine"
)

// cleanupDoc represents a potentially large set of documents that should be
// removed.
type cleanupDoc struct {
	Id     bson.ObjectId `bson:"_id"`
	Kind   cleanupKind
	Prefix string
}

// newCleanupOp returns a txn.Op that creates a cleanup document with a unique
// id and the supplied kind and prefix.
func (st *State) newCleanupOp(kind cleanupKind, prefix string) txn.Op {
	doc := &cleanupDoc{
		Id:     bson.NewObjectId(),
		Kind:   kind,
		Prefix: prefix,
	}
	return txn.Op{
		C:      st.cleanups.Name,
		Id:     doc.Id,
		Insert: doc,
	}
}

// NeedsCleanup returns true if documents previously marked for removal exist.
func (st *State) NeedsCleanup() (bool, error) {
	count, err := st.cleanups.Count()
	if err != nil {
		return false, err
	}
	return count > 0, nil
}

// Cleanup removes all documents that were previously marked for removal, if
// any such exist. It should be called periodically by at least one element
// of the system.
func (st *State) Cleanup() error {
	doc := cleanupDoc{}
	iter := st.cleanups.Find(nil).Iter()
	for iter.Next(&doc) {
		var err error
		logger.Debugf("running %q cleanup: %q", doc.Kind, doc.Prefix)
		switch doc.Kind {
		case cleanupRelationSettings:
			err = st.cleanupRelationSettings(doc.Prefix)
		case cleanupUnitsForDyingService:
			err = st.cleanupUnitsForDyingService(doc.Prefix)
		case cleanupDyingUnit:
			err = st.cleanupDyingUnit(doc.Prefix)
		case cleanupRemovedUnit:
			err = st.cleanupRemovedUnit(doc.Prefix)
		case cleanupServicesForDyingEnvironment:
			err = st.cleanupServicesForDyingEnvironment()
		case cleanupForceDestroyedMachine:
			err = st.cleanupForceDestroyedMachine(doc.Prefix)
		default:
			err = fmt.Errorf("unknown cleanup kind %q", doc.Kind)
		}
		if err != nil {
			logger.Warningf("cleanup failed: %v", err)
			continue
		}
		ops := []txn.Op{{
			C:      st.cleanups.Name,
			Id:     doc.Id,
			Remove: true,
		}}
		if err := st.runTransaction(ops); err != nil {
			logger.Warningf("cannot remove empty cleanup document: %v", err)
		}
	}
	if err := iter.Err(); err != nil {
		return fmt.Errorf("cannot read cleanup document: %v", err)
	}
	return nil
}

func (st *State) cleanupRelationSettings(prefix string) error {
	// Documents marked for cleanup are not otherwise referenced in the
	// system, and will not be under watch, and are therefore safe to
	// delete directly.
	sel := bson.D{{"_id", bson.D{{"$regex", "^" + prefix}}}}
	if count, err := st.settings.Find(sel).Count(); err != nil {
		return fmt.Errorf("cannot detect cleanup targets: %v", err)
	} else if count != 0 {
		if _, err := st.settings.RemoveAll(sel); err != nil {
			return fmt.Errorf("cannot remove documents marked for cleanup: %v", err)
		}
	}
	return nil
}

// cleanupServicesForDyingEnvironment sets all services to Dying, if they are
// not already Dying or Dead. It's expected to be used when an environment is
// destroyed.
func (st *State) cleanupServicesForDyingEnvironment() error {
	// This won't miss services, because a Dying environment cannot have
	// services added to it. But we do have to remove the services themselves
	// via individual transactions, because they could be in any state at all.
	service := &Service{st: st}
	sel := bson.D{{"life", Alive}}
	iter := st.services.Find(sel).Iter()
	for iter.Next(&service.doc) {
		if err := service.Destroy(); err != nil {
			return err
		}
	}
	if err := iter.Err(); err != nil {
		return fmt.Errorf("cannot read service document: %v", err)
	}
	return nil
}

// cleanupUnitsForDyingService sets all units with the given prefix to Dying,
// if they are not already Dying or Dead. It's expected to be used when a
// service is destroyed.
func (st *State) cleanupUnitsForDyingService(prefix string) error {
	// This won't miss units, because a Dying service cannot have units added
	// to it. But we do have to remove the units themselves via individual
	// transactions, because they could be in any state at all.
	unit := &Unit{st: st}
	sel := bson.D{{"_id", bson.D{{"$regex", "^" + prefix}}}, {"life", Alive}}
	iter := st.units.Find(sel).Iter()
	for iter.Next(&unit.doc) {
		if err := unit.Destroy(); err != nil {
			return err
		}
	}
	if err := iter.Err(); err != nil {
		return fmt.Errorf("cannot read unit document: %v", err)
	}
	return nil
}

// cleanupDyingUnit marks the unit as departing from all its joined relations,
// allowing related units to start converging to a state in which that unit is
// gone as quickly as possible.
func (st *State) cleanupDyingUnit(name string) error {
	unit, err := st.Unit(name)
	if errors.IsNotFound(err) {
		return nil
	} else if err != nil {
		return err
	}
	relations, err := unit.RelationsJoined()
	if err != nil {
		return err
	}
	for _, relation := range relations {
		relationUnit, err := relation.Unit(unit)
		if errors.IsNotFound(err) {
			continue
		} else if err != nil {
			return err
		}
		if err := relationUnit.PrepareLeaveScope(); err != nil {
			return err
		}
	}
	return nil
}

// cleanupRemovedUnit takes care of all the final cleanup required when
// a unit is removed.
func (st *State) cleanupRemovedUnit(name string) error {
	// st.Unit(name) is likely to remove 'IsNotFound' here, but we still
	// need to cleanup actions, so we just get them from the state using
	// the unit Name
	actions, err := st.UnitActions(name)
	if err != nil {
		return err
	}
	for _, action := range actions {
		if err = action.Fail("unit removed"); err != nil {
			return err
		}
	}
	return nil
}

// cleanupForceDestroyedMachine systematically destroys and removes all entities
// that depend upon the supplied machine, and removes the machine from state. It's
// expected to be used in response to destroy-machine --force.
func (st *State) cleanupForceDestroyedMachine(machineId string) error {
	machine, err := st.Machine(machineId)
	if errors.IsNotFound(err) {
		return nil
	} else if err != nil {
		return err
	}
	// In an ideal world, we'd call machine.Destroy() here, and thus prevent
	// new dependencies being added while we clean up the ones we know about.
	// But machine destruction is unsophisticated, and doesn't allow for
	// destruction while dependencies exist; so we just have to deal with that
	// possibility below.
	if err := st.cleanupContainers(machine); err != nil {
		return err
	}
	for _, unitName := range machine.doc.Principals {
		if err := st.obliterateUnit(unitName); err != nil {
			return err
		}
	}
	// We need to refresh the machine at this point, because the local copy
	// of the document will not reflect changes caused by the unit cleanups
	// above, and may thus fail immediately.
	if err := machine.Refresh(); errors.IsNotFound(err) {
		return nil
	} else if err != nil {
		return err
	}
	// TODO(fwereade): 2013-11-11 bug 1250104
	// If this fails, it's *probably* due to a race in which new dependencies
	// were added while we cleaned up the old ones. If the cleanup doesn't run
	// again -- which it *probably* will anyway -- the issue can be resolved by
	// force-destroying the machine again; that's better than adding layer
	// upon layer of complication here.
	return machine.EnsureDead()

	// Note that we do *not* remove the machine entirely: we leave it for the
	// provisioner to clean up, so that we don't end up with an unreferenced
	// instance that would otherwise be ignored when in provisioner-safe-mode.
}

// cleanupContainers recursively calls cleanupForceDestroyedMachine on the supplied
// machine's containers, and removes them from state entirely.
func (st *State) cleanupContainers(machine *Machine) error {
	containerIds, err := machine.Containers()
	if errors.IsNotFound(err) {
		return nil
	} else if err != nil {
		return err
	}
	for _, containerId := range containerIds {
		if err := st.cleanupForceDestroyedMachine(containerId); err != nil {
			return err
		}
		container, err := st.Machine(containerId)
		if errors.IsNotFound(err) {
			return nil
		} else if err != nil {
			return err
		}
		if err := container.Remove(); err != nil {
			return err
		}
	}
	return nil
}

// obliterateUnit removes a unit from state completely. It is not safe or
// sane to obliterate any unit in isolation; its only reasonable use is in
// the context of machine obliteration, in which we can be sure that unclean
// shutdown of units is not going to leave a machine in a difficult state.
func (st *State) obliterateUnit(unitName string) error {
	unit, err := st.Unit(unitName)
	if errors.IsNotFound(err) {
		return nil
	} else if err != nil {
		return err
	}
	// Unlike the machine, we *can* always destroy the unit, and (at least)
	// prevent further dependencies being added. If we're really lucky, the
	// unit will be removed immediately.
	if err := unit.Destroy(); err != nil {
		return err
	}
	if err := unit.Refresh(); errors.IsNotFound(err) {
		return nil
	} else if err != nil {
		return err
	}
	for _, subName := range unit.SubordinateNames() {
		if err := st.obliterateUnit(subName); err != nil {
			return err
		}
	}
	if err := unit.EnsureDead(); err != nil {
		return err
	}
	return unit.Remove()
}
<commit_msg>remove comment, irrelevant now<commit_after>// Copyright 2014 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package state

import (
	"fmt"

	"github.com/juju/errors"
	"labix.org/v2/mgo/bson"
	"labix.org/v2/mgo/txn"
)

type cleanupKind string

const (
	// SCHEMACHANGE: the names are expressive, the values not so much.
	cleanupRelationSettings            cleanupKind = "settings"
	cleanupUnitsForDyingService        cleanupKind = "units"
	cleanupDyingUnit                   cleanupKind = "dyingUnit"
	cleanupRemovedUnit                 cleanupKind = "removedUnit"
	cleanupServicesForDyingEnvironment cleanupKind = "services"
	cleanupForceDestroyedMachine       cleanupKind = "machine"
)

// cleanupDoc represents a potentially large set of documents that should be
// removed.
type cleanupDoc struct {
	Id     bson.ObjectId `bson:"_id"`
	Kind   cleanupKind
	Prefix string
}

// newCleanupOp returns a txn.Op that creates a cleanup document with a unique
// id and the supplied kind and prefix.
func (st *State) newCleanupOp(kind cleanupKind, prefix string) txn.Op {
	doc := &cleanupDoc{
		Id:     bson.NewObjectId(),
		Kind:   kind,
		Prefix: prefix,
	}
	return txn.Op{
		C:      st.cleanups.Name,
		Id:     doc.Id,
		Insert: doc,
	}
}

// NeedsCleanup returns true if documents previously marked for removal exist.
func (st *State) NeedsCleanup() (bool, error) {
	count, err := st.cleanups.Count()
	if err != nil {
		return false, err
	}
	return count > 0, nil
}

// Cleanup removes all documents that were previously marked for removal, if
// any such exist. It should be called periodically by at least one element
// of the system.
func (st *State) Cleanup() error {
	doc := cleanupDoc{}
	iter := st.cleanups.Find(nil).Iter()
	for iter.Next(&doc) {
		var err error
		logger.Debugf("running %q cleanup: %q", doc.Kind, doc.Prefix)
		switch doc.Kind {
		case cleanupRelationSettings:
			err = st.cleanupRelationSettings(doc.Prefix)
		case cleanupUnitsForDyingService:
			err = st.cleanupUnitsForDyingService(doc.Prefix)
		case cleanupDyingUnit:
			err = st.cleanupDyingUnit(doc.Prefix)
		case cleanupRemovedUnit:
			err = st.cleanupRemovedUnit(doc.Prefix)
		case cleanupServicesForDyingEnvironment:
			err = st.cleanupServicesForDyingEnvironment()
		case cleanupForceDestroyedMachine:
			err = st.cleanupForceDestroyedMachine(doc.Prefix)
		default:
			err = fmt.Errorf("unknown cleanup kind %q", doc.Kind)
		}
		if err != nil {
			logger.Warningf("cleanup failed: %v", err)
			continue
		}
		ops := []txn.Op{{
			C:      st.cleanups.Name,
			Id:     doc.Id,
			Remove: true,
		}}
		if err := st.runTransaction(ops); err != nil {
			logger.Warningf("cannot remove empty cleanup document: %v", err)
		}
	}
	if err := iter.Err(); err != nil {
		return fmt.Errorf("cannot read cleanup document: %v", err)
	}
	return nil
}

func (st *State) cleanupRelationSettings(prefix string) error {
	// Documents marked for cleanup are not otherwise referenced in the
	// system, and will not be under watch, and are therefore safe to
	// delete directly.
	sel := bson.D{{"_id", bson.D{{"$regex", "^" + prefix}}}}
	if count, err := st.settings.Find(sel).Count(); err != nil {
		return fmt.Errorf("cannot detect cleanup targets: %v", err)
	} else if count != 0 {
		if _, err := st.settings.RemoveAll(sel); err != nil {
			return fmt.Errorf("cannot remove documents marked for cleanup: %v", err)
		}
	}
	return nil
}

// cleanupServicesForDyingEnvironment sets all services to Dying, if they are
// not already Dying or Dead. It's expected to be used when an environment is
// destroyed.
func (st *State) cleanupServicesForDyingEnvironment() error {
	// This won't miss services, because a Dying environment cannot have
	// services added to it. But we do have to remove the services themselves
	// via individual transactions, because they could be in any state at all.
	service := &Service{st: st}
	sel := bson.D{{"life", Alive}}
	iter := st.services.Find(sel).Iter()
	for iter.Next(&service.doc) {
		if err := service.Destroy(); err != nil {
			return err
		}
	}
	if err := iter.Err(); err != nil {
		return fmt.Errorf("cannot read service document: %v", err)
	}
	return nil
}

// cleanupUnitsForDyingService sets all units with the given prefix to Dying,
// if they are not already Dying or Dead. It's expected to be used when a
// service is destroyed.
func (st *State) cleanupUnitsForDyingService(prefix string) error {
	// This won't miss units, because a Dying service cannot have units added
	// to it. But we do have to remove the units themselves via individual
	// transactions, because they could be in any state at all.
	unit := &Unit{st: st}
	sel := bson.D{{"_id", bson.D{{"$regex", "^" + prefix}}}, {"life", Alive}}
	iter := st.units.Find(sel).Iter()
	for iter.Next(&unit.doc) {
		if err := unit.Destroy(); err != nil {
			return err
		}
	}
	if err := iter.Err(); err != nil {
		return fmt.Errorf("cannot read unit document: %v", err)
	}
	return nil
}

// cleanupDyingUnit marks the unit as departing from all its joined relations,
// allowing related units to start converging to a state in which that unit is
// gone as quickly as possible.
func (st *State) cleanupDyingUnit(name string) error {
	unit, err := st.Unit(name)
	if errors.IsNotFound(err) {
		return nil
	} else if err != nil {
		return err
	}
	relations, err := unit.RelationsJoined()
	if err != nil {
		return err
	}
	for _, relation := range relations {
		relationUnit, err := relation.Unit(unit)
		if errors.IsNotFound(err) {
			continue
		} else if err != nil {
			return err
		}
		if err := relationUnit.PrepareLeaveScope(); err != nil {
			return err
		}
	}
	return nil
}

// cleanupRemovedUnit takes care of all the final cleanup required when
// a unit is removed.
func (st *State) cleanupRemovedUnit(name string) error {
	actions, err := st.UnitActions(name)
	if err != nil {
		return err
	}
	for _, action := range actions {
		if err = action.Fail("unit removed"); err != nil {
			return err
		}
	}
	return nil
}

// cleanupForceDestroyedMachine systematically destroys and removes all entities
// that depend upon the supplied machine, and removes the machine from state. It's
// expected to be used in response to destroy-machine --force.
func (st *State) cleanupForceDestroyedMachine(machineId string) error {
	machine, err := st.Machine(machineId)
	if errors.IsNotFound(err) {
		return nil
	} else if err != nil {
		return err
	}
	// In an ideal world, we'd call machine.Destroy() here, and thus prevent
	// new dependencies being added while we clean up the ones we know about.
	// But machine destruction is unsophisticated, and doesn't allow for
	// destruction while dependencies exist; so we just have to deal with that
	// possibility below.
	if err := st.cleanupContainers(machine); err != nil {
		return err
	}
	for _, unitName := range machine.doc.Principals {
		if err := st.obliterateUnit(unitName); err != nil {
			return err
		}
	}
	// We need to refresh the machine at this point, because the local copy
	// of the document will not reflect changes caused by the unit cleanups
	// above, and may thus fail immediately.
	if err := machine.Refresh(); errors.IsNotFound(err) {
		return nil
	} else if err != nil {
		return err
	}
	// TODO(fwereade): 2013-11-11 bug 1250104
	// If this fails, it's *probably* due to a race in which new dependencies
	// were added while we cleaned up the old ones. If the cleanup doesn't run
	// again -- which it *probably* will anyway -- the issue can be resolved by
	// force-destroying the machine again; that's better than adding layer
	// upon layer of complication here.
	return machine.EnsureDead()

	// Note that we do *not* remove the machine entirely: we leave it for the
	// provisioner to clean up, so that we don't end up with an unreferenced
	// instance that would otherwise be ignored when in provisioner-safe-mode.
}

// cleanupContainers recursively calls cleanupForceDestroyedMachine on the supplied
// machine's containers, and removes them from state entirely.
func (st *State) cleanupContainers(machine *Machine) error {
	containerIds, err := machine.Containers()
	if errors.IsNotFound(err) {
		return nil
	} else if err != nil {
		return err
	}
	for _, containerId := range containerIds {
		if err := st.cleanupForceDestroyedMachine(containerId); err != nil {
			return err
		}
		container, err := st.Machine(containerId)
		if errors.IsNotFound(err) {
			return nil
		} else if err != nil {
			return err
		}
		if err := container.Remove(); err != nil {
			return err
		}
	}
	return nil
}

// obliterateUnit removes a unit from state completely. It is not safe or
// sane to obliterate any unit in isolation; its only reasonable use is in
// the context of machine obliteration, in which we can be sure that unclean
// shutdown of units is not going to leave a machine in a difficult state.
func (st *State) obliterateUnit(unitName string) error {
	unit, err := st.Unit(unitName)
	if errors.IsNotFound(err) {
		return nil
	} else if err != nil {
		return err
	}
	// Unlike the machine, we *can* always destroy the unit, and (at least)
	// prevent further dependencies being added. If we're really lucky, the
	// unit will be removed immediately.
	if err := unit.Destroy(); err != nil {
		return err
	}
	if err := unit.Refresh(); errors.IsNotFound(err) {
		return nil
	} else if err != nil {
		return err
	}
	for _, subName := range unit.SubordinateNames() {
		if err := st.obliterateUnit(subName); err != nil {
			return err
		}
	}
	if err := unit.EnsureDead(); err != nil {
		return err
	}
	return unit.Remove()
}
<|endoftext|>


<commit_before>package state

import (
	"github.com/fluffle/goirc/logging"
)

// The state manager interface
type StateTracker interface {
	// Nick methods
	NewNick(nick string) *Nick
	GetNick(nick string) *Nick
	ReNick(old, neu string)
	DelNick(nick string)
	// Channel methods
	NewChannel(channel string) *Channel
	GetChannel(channel string) *Channel
	DelChannel(channel string)
	// Information about ME!
	Me() *Nick
	// And the tracking operations
	IsOn(channel, nick string) bool
	Associate(channel *Channel, nick *Nick)
	Dissociate(channel *Channel, nick *Nick)
}

// ... and a struct to implement it ...
type stateTracker struct {
	// Map of channels we're on
	chans map[string]*Channel
	// Map of nicks we know about
	nicks map[string]*Nick

	// We need to keep state on who we are :-)
	me *Nick
}

// ... and finally a constructor to make it.
func NewTracker(mynick string) *stateTracker {
	st := &stateTracker{
		chans: make(map[string]*Channel),
		nicks: make(map[string]*Nick),
	}
	st.me = st.NewNick(mynick)
	return st
}

/******************************************************************************\
 * tracker methods to create/look up nicks/channels
\******************************************************************************/

// Creates a new Nick, initialises it, and stores it so it
// can be properly tracked for state management purposes.
func (st *stateTracker) NewNick(n string) *Nick {
	if _, ok := st.nicks[n]; ok {
		logging.Warn("StateTracker.NewNick(): %s already tracked.", n)
		return nil
	}
	st.nicks[n] = NewNick(n)
	return st.nicks[n]
}

// Returns a Nick for the nick n, if we're tracking it.
func (st *stateTracker) GetNick(n string) *Nick {
	if nk, ok := st.nicks[n]; ok {
		return nk
	}
	return nil
}

// Signals to the tracker that a Nick should be tracked
// under a "neu" nick rather than the old one.
func (st *stateTracker) ReNick(old, neu string) {
	if nk, ok := st.nicks[old]; ok {
		if _, ok := st.nicks[neu]; !ok {
			st.nicks[old] = nil, false
			nk.Nick = neu
			st.nicks[neu] = nk
		} else {
			logging.Warn("StateTracker.ReNick(): %s already exists.", neu)
		}
	} else {
		logging.Warn("StateTracker.ReNick(): %s not tracked.", old)
	}
}

// Removes a Nick from being tracked.
func (st *stateTracker) DelNick(n string) {
	if nk, ok := st.nicks[n]; ok {
		if nk != st.me {
			st.delNick(nk)
		} else {
			logging.Warn("StateTracker.DelNick(): won't delete myself.")
		}
	} else {
		logging.Warn("StateTracker.DelNick(): %s not tracked.", n)
	}
}

func (st *stateTracker) delNick(nk *Nick) {
	if nk == st.me {
		// Shouldn't get here => internal state tracking code is fubar.
		logging.Error("StateTracker.DelNick(): TRYING TO DELETE ME :-(")
		return
	}
	st.nicks[nk.Nick] = nil, false
	for ch, _ := range nk.chans {
		nk.delChannel(ch)
		ch.delNick(nk)
		if len(ch.nicks) == 0 {
			// Deleting a nick from tracking shouldn't empty any channels as
			// *we* should be on the channel with them to be tracking them.
			logging.Error("StateTracker.delNick(): deleting nick %s emptied "+
				"channel %s, this shouldn't happen", nk.Nick, ch.Name)
		}
	}
}

// Creates a new Channel, initialises it, and stores it so it
// can be properly tracked for state management purposes.
func (st *stateTracker) NewChannel(c string) *Channel {
	if _, ok := st.chans[c]; ok {
		logging.Warn("StateTracker.NewChannel(): %s already tracked", c)
		return nil
	}
	st.chans[c] = NewChannel(c)
	return st.chans[c]
}

// Returns a Channel for the channel c, if we're tracking it.
func (st *stateTracker) GetChannel(c string) *Channel {
	if ch, ok := st.chans[c]; ok {
		return ch
	}
	return nil
}

// Removes a Channel from being tracked.
func (st *stateTracker) DelChannel(c string) {
	if ch, ok := st.chans[c]; ok {
		st.delChannel(ch)
	} else {
		logging.Warn("StateTracker.DelChannel(): %s not tracked.", c)
	}
}

func (st *stateTracker) delChannel(ch *Channel) {
	st.chans[ch.Name] = nil, false
	for nk, _ := range ch.nicks {
		ch.delNick(nk)
		nk.delChannel(ch)
		if len(nk.chans) == 0 && nk != st.me {
			// We're no longer in any channels with this nick.
			st.delNick(nk)
		}
	}
}

// Returns the Nick the state tracker thinks is Me.
func (st *stateTracker) Me() *Nick {
	return st.me
}

// Returns true if both the channel c and the nick n are tracked
// and the nick is associated with the channel.
func (st *stateTracker) IsOn(c, n string) bool {
	nk := st.GetNick(n)
	ch := st.GetChannel(c)
	if nk != nil && ch != nil {
		return nk.IsOn(ch)
	}
	return false
}

// Associates an already known nick with an already known channel.
func (st *stateTracker) Associate(ch *Channel, nk *Nick) {
	if ch == nil || nk == nil {
		logging.Error("StateTracker.Associate(): passed nil values :-(")
		return
	}
	if nk.IsOn(ch) {
		logging.Warn("StateTracker.Associate(): %s already on %s.",
			nk.Nick, ch.Name)
		return
	}
	cp := new(ChanPrivs)
	ch.addNick(nk, cp)
	nk.addChannel(ch, cp)
}

// Dissociates an already known nick from an already known channel.
// Does some tidying up to stop tracking nicks we're no longer on
// any common channels with, and channels we're no longer on.
func (st *stateTracker) Dissociate(ch *Channel, nk *Nick) {
	switch {
	case ch == nil || nk == nil:
		logging.Error("StateTracker.Dissociate(): passed nil values :-(")
	case !nk.IsOn(ch):
		logging.Warn("StateTracker.Dissociate(): %s not on %s.",
			nk.Nick, ch.Name)
	case nk == st.me:
		// I'm leaving the channel for some reason, so it won't be tracked.
		st.delChannel(ch)
	default:
		// Remove the nick from the channel and the channel from the nick.
		ch.delNick(nk)
		nk.delChannel(ch)
		if len(nk.chans) == 0 {
			// We're no longer in any channels with this nick.
			st.delNick(nk)
		}
	}
}
<commit_msg>Oops, missed a bit.<commit_after>package state

import (
	"github.com/fluffle/goirc/logging"
)

// The state manager interface
type StateTracker interface {
	// Nick methods
	NewNick(nick string) *Nick
	GetNick(nick string) *Nick
	ReNick(old, neu string)
	DelNick(nick string)
	// Channel methods
	NewChannel(channel string) *Channel
	GetChannel(channel string) *Channel
	DelChannel(channel string)
	// Information about ME!
	Me() *Nick
	// And the tracking operations
	IsOn(channel, nick string) bool
	Associate(channel *Channel, nick *Nick)
	Dissociate(channel *Channel, nick *Nick)
}

// ... and a struct to implement it ...
type stateTracker struct {
	// Map of channels we're on
	chans map[string]*Channel
	// Map of nicks we know about
	nicks map[string]*Nick

	// We need to keep state on who we are :-)
	me *Nick
}

// ... and finally a constructor to make it.
func NewTracker(mynick string) *stateTracker {
	st := &stateTracker{
		chans: make(map[string]*Channel),
		nicks: make(map[string]*Nick),
	}
	st.me = st.NewNick(mynick)
	return st
}

/******************************************************************************\
 * tracker methods to create/look up nicks/channels
\******************************************************************************/

// Creates a new Nick, initialises it, and stores it so it
// can be properly tracked for state management purposes.
func (st *stateTracker) NewNick(n string) *Nick {
	if _, ok := st.nicks[n]; ok {
		logging.Warn("StateTracker.NewNick(): %s already tracked.", n)
		return nil
	}
	st.nicks[n] = NewNick(n)
	return st.nicks[n]
}

// Returns a Nick for the nick n, if we're tracking it.
func (st *stateTracker) GetNick(n string) *Nick {
	if nk, ok := st.nicks[n]; ok {
		return nk
	}
	return nil
}

// Signals to the tracker that a Nick should be tracked
// under a "neu" nick rather than the old one.
func (st *stateTracker) ReNick(old, neu string) {
	if nk, ok := st.nicks[old]; ok {
		if _, ok := st.nicks[neu]; !ok {
			nk.Nick = neu
			st.nicks[old] = nil, false
			st.nicks[neu] = nk
			for ch, _ := range nk.chans {
				// We also need to update the lookup maps of all the channels
				// the nick is on, to keep things in sync.
				ch.lookup[old] = nil, false
				ch.lookup[neu] = nk
			}
		} else {
			logging.Warn("StateTracker.ReNick(): %s already exists.", neu)
		}
	} else {
		logging.Warn("StateTracker.ReNick(): %s not tracked.", old)
	}
}

// Removes a Nick from being tracked.
func (st *stateTracker) DelNick(n string) {
	if nk, ok := st.nicks[n]; ok {
		if nk != st.me {
			st.delNick(nk)
		} else {
			logging.Warn("StateTracker.DelNick(): won't delete myself.")
		}
	} else {
		logging.Warn("StateTracker.DelNick(): %s not tracked.", n)
	}
}

func (st *stateTracker) delNick(nk *Nick) {
	if nk == st.me {
		// Shouldn't get here => internal state tracking code is fubar.
		logging.Error("StateTracker.DelNick(): TRYING TO DELETE ME :-(")
		return
	}
	st.nicks[nk.Nick] = nil, false
	for ch, _ := range nk.chans {
		nk.delChannel(ch)
		ch.delNick(nk)
		if len(ch.nicks) == 0 {
			// Deleting a nick from tracking shouldn't empty any channels as
			// *we* should be on the channel with them to be tracking them.
			logging.Error("StateTracker.delNick(): deleting nick %s emptied "+
				"channel %s, this shouldn't happen", nk.Nick, ch.Name)
		}
	}
}

// Creates a new Channel, initialises it, and stores it so it
// can be properly tracked for state management purposes.
func (st *stateTracker) NewChannel(c string) *Channel {
	if _, ok := st.chans[c]; ok {
		logging.Warn("StateTracker.NewChannel(): %s already tracked", c)
		return nil
	}
	st.chans[c] = NewChannel(c)
	return st.chans[c]
}

// Returns a Channel for the channel c, if we're tracking it.
func (st *stateTracker) GetChannel(c string) *Channel {
	if ch, ok := st.chans[c]; ok {
		return ch
	}
	return nil
}

// Removes a Channel from being tracked.
func (st *stateTracker) DelChannel(c string) {
	if ch, ok := st.chans[c]; ok {
		st.delChannel(ch)
	} else {
		logging.Warn("StateTracker.DelChannel(): %s not tracked.", c)
	}
}

func (st *stateTracker) delChannel(ch *Channel) {
	st.chans[ch.Name] = nil, false
	for nk, _ := range ch.nicks {
		ch.delNick(nk)
		nk.delChannel(ch)
		if len(nk.chans) == 0 && nk != st.me {
			// We're no longer in any channels with this nick.
			st.delNick(nk)
		}
	}
}

// Returns the Nick the state tracker thinks is Me.
func (st *stateTracker) Me() *Nick {
	return st.me
}

// Returns true if both the channel c and the nick n are tracked
// and the nick is associated with the channel.
func (st *stateTracker) IsOn(c, n string) bool {
	nk := st.GetNick(n)
	ch := st.GetChannel(c)
	if nk != nil && ch != nil {
		return nk.IsOn(ch)
	}
	return false
}

// Associates an already known nick with an already known channel.
func (st *stateTracker) Associate(ch *Channel, nk *Nick) {
	if ch == nil || nk == nil {
		logging.Error("StateTracker.Associate(): passed nil values :-(")
		return
	}
	if nk.IsOn(ch) {
		logging.Warn("StateTracker.Associate(): %s already on %s.",
			nk.Nick, ch.Name)
		return
	}
	cp := new(ChanPrivs)
	ch.addNick(nk, cp)
	nk.addChannel(ch, cp)
}

// Dissociates an already known nick from an already known channel.
// Does some tidying up to stop tracking nicks we're no longer on
// any common channels with, and channels we're no longer on.
func (st *stateTracker) Dissociate(ch *Channel, nk *Nick) {
	switch {
	case ch == nil || nk == nil:
		logging.Error("StateTracker.Dissociate(): passed nil values :-(")
	case !nk.IsOn(ch):
		logging.Warn("StateTracker.Dissociate(): %s not on %s.",
			nk.Nick, ch.Name)
	case nk == st.me:
		// I'm leaving the channel for some reason, so it won't be tracked.
		st.delChannel(ch)
	default:
		// Remove the nick from the channel and the channel from the nick.
		ch.delNick(nk)
		nk.delChannel(ch)
		if len(nk.chans) == 0 {
			// We're no longer in any channels with this nick.
			st.delNick(nk)
		}
	}
}
<|endoftext|>


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

package system

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"strings"

	"github.com/vtolstov/go-ioctl"
)

func ResizeRootFS() error {
	var err error
	var stdout io.ReadCloser
	var stdin bytes.Buffer

	output, err := exec.Command("findmnt", "-n", "-o", "source", "/").CombinedOutput()
	if err != nil {
		return err
	}

	mountpoint := strings.TrimSpace(string(output))
	partstart := "2048"
	device := mountpoint[:len(mountpoint)-1]
	partition := mountpoint[len(mountpoint)-1:]

	cmd := exec.Command("fdisk", "-l", "-u", device)
	stdout, err = cmd.StdoutPipe()
	if err != nil {
		log.Printf("failed to open %s via fdisk %s 2\n", device, err.Error())
		return err
	}
	r := bufio.NewReader(stdout)

	if err = cmd.Start(); err != nil {
		log.Printf("failed to open %s via fdisk %s 3\n", device, err.Error())
		return err
	}

	for {
		line, err := r.ReadString('\n')
		if err != nil {
			break
		}

		if strings.HasPrefix(line, device+partition) {
			ps := strings.Fields(line) // /dev/sda1      *      4096   251658239   125827072  83 Linux
			if ps[1] == "*" {
				partstart = ps[2]
			} else {
				partstart = ps[1]
			}
		}
	}

	if err = cmd.Wait(); err != nil || partstart == "" {
		return fmt.Errorf("failed to open %s via fdisk 4\n", device)
	}

	stdin.Write([]byte("o\nn\np\n1\n" + partstart + "\n\na\n1\nw\n"))
	cmd = exec.Command("fdisk", "-u", device)
	cmd.Stdin = &stdin
	cmd.Run()
	stdin.Reset()

	w, err := os.OpenFile(device, os.O_WRONLY, 0600)
	if err == nil {
		defer w.Close()
		err = ioctl.BlkRRPart(w.Fd())
		if err == nil {
			return exec.Command("resize2fs", device+partition).Run()
		}
	}
	for _, name := range []string{"partprobe", "kpartx"} {
		if _, err = exec.LookPath(name); err == nil {
			if err = exec.Command(name, device).Run(); err == nil {
				return nil
			}
		}
	}
	return exec.Command("resize2fs", device+partition).Run()
}
<commit_msg>add ubuntu workaround<commit_after>// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package system

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"strings"

	"github.com/vtolstov/go-ioctl"
)

func ResizeRootFS() error {
	var err error
	var stdout io.ReadCloser
	var stdin bytes.Buffer

	output, err := exec.Command("findmnt", "-n", "-o", "source", "/").CombinedOutput()
	if err != nil {
		return err
	}

	mountpoint := strings.TrimSpace(string(output))
	partstart := "2048"
	device := mountpoint[:len(mountpoint)-1]
	partition := mountpoint[len(mountpoint)-1:]

	cmd := exec.Command("fdisk", "-l", "-u", device)
	stdout, err = cmd.StdoutPipe()
	if err != nil {
		log.Printf("failed to open %s via fdisk %s 2\n", device, err.Error())
		return err
	}
	r := bufio.NewReader(stdout)

	if err = cmd.Start(); err != nil {
		log.Printf("failed to open %s via fdisk %s 3\n", device, err.Error())
		return err
	}

	for {
		line, err := r.ReadString('\n')
		if err != nil {
			break
		}

		if strings.HasPrefix(line, device+partition) {
			ps := strings.Fields(line) // /dev/sda1      *      4096   251658239   125827072  83 Linux
			if ps[1] == "*" {
				partstart = ps[2]
			} else {
				partstart = ps[1]
			}
		}
	}

	if err = cmd.Wait(); err != nil || partstart == "" {
		return fmt.Errorf("failed to open %s via fdisk 4\n", device)
	}

	stdin.Write([]byte("o\nn\np\n1\n" + partstart + "\n\na\n1\nw\n"))
	cmd = exec.Command("fdisk", "-u", device)
	cmd.Stdin = &stdin
	cmd.Run()
	stdin.Reset()

	w, err := os.OpenFile(device, os.O_WRONLY, 0600)
	if err == nil {
		defer w.Close()
		err = ioctl.BlkRRPart(w.Fd())
		if err == nil {
			return exec.Command("resize2fs", device+partition).Run()
		}
	}

	args := []string{}
	for _, name := range []string{"partx", "partprobe", "kpartx"} {
		if _, err = exec.LookPath(name); err == nil {
			switch name {
			case "partx":
				args = []string{"-u", device}
			default:
				args = []string{device}
			}
			log.Printf("update partition table via %s %s", name, strings.Join(args, " "))
			exec.Command(name, args...).Run()
		}
	}
	log.Printf("resize filesystem via %s %s", "resize2fs", device+partition)
	err = exec.Command("resize2fs", device+partition).Run()
	if err != nil {
		return err
	}
	for _, name := range []string{"grub-install", "grub2-install"} {
		if _, err = exec.LookPath(name); err == nil {
			log.Printf("reinstall grub %s %s", name, device)
			exec.Command(name, device).Run()
		}
	}
}
<|endoftext|>


<commit_before>package utils

import (
	"bytes"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"syscall"

	"github.com/containers/podman/v3/pkg/lookup"
	"github.com/cri-o/cri-o/server/cri/types"
	securejoin "github.com/cyphar/filepath-securejoin"
	"github.com/opencontainers/runc/libcontainer/user"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"

	systemdDbus "github.com/coreos/go-systemd/v22/dbus"
	"github.com/godbus/dbus/v5"
)

// ExecCmd executes a command with args and returns its output as a string along
// with an error, if any
func ExecCmd(name string, args ...string) (string, error) {
	cmd := exec.Command(name, args...)
	var stdout bytes.Buffer
	var stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	if v, found := os.LookupEnv("XDG_RUNTIME_DIR"); found {
		cmd.Env = append(cmd.Env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", v))
	}

	err := cmd.Run()
	if err != nil {
		return "", fmt.Errorf("`%v %v` failed: %v %v (%v)", name, strings.Join(args, " "), stderr.String(), stdout.String(), err)
	}

	return stdout.String(), nil
}

// StatusToExitCode converts wait status code to an exit code
func StatusToExitCode(status int) int {
	return ((status) & 0xff00) >> 8
}

// RunUnderSystemdScope adds the specified pid to a systemd scope
func RunUnderSystemdScope(pid int, slice, unitName string, properties ...systemdDbus.Property) error {
	conn, err := systemdDbus.New()
	if err != nil {
		return err
	}
	defaultProperties := []systemdDbus.Property{
		newProp("PIDs", []uint32{uint32(pid)}),
		newProp("Delegate", true),
		newProp("DefaultDependencies", false),
	}
	properties = append(defaultProperties, properties...)
	if slice != "" {
		properties = append(properties, systemdDbus.PropSlice(slice))
	}
	ch := make(chan string)
	_, err = conn.StartTransientUnit(unitName, "replace", properties, ch)
	if err != nil {
		return err
	}
	defer conn.Close()

	// Block until job is started
	<-ch

	return nil
}

func newProp(name string, units interface{}) systemdDbus.Property {
	return systemdDbus.Property{
		Name:  name,
		Value: dbus.MakeVariant(units),
	}
}

// DetachError is special error which returned in case of container detach.
type DetachError struct{}

func (DetachError) Error() string {
	return "detached from container"
}

// CopyDetachable is similar to io.Copy but support a detach key sequence to break out.
func CopyDetachable(dst io.Writer, src io.Reader, keys []byte) (int64, error) {
	var (
		written int64
		err     error
	)
	// Sanity check interfaces
	if dst == nil || src == nil {
		return 0, fmt.Errorf("src/dst reader/writer nil")
	}
	if len(keys) == 0 {
		// Default keys : ctrl-p ctrl-q
		keys = []byte{16, 17}
	}

	buf := make([]byte, 32*1024)
	for {
		nr, er := src.Read(buf)
		if nr > 0 {
			preservBuf := []byte{}
			for i, key := range keys {
				preservBuf = append(preservBuf, buf[0:nr]...)
				if nr != 1 || buf[0] != key {
					break
				}
				if i == len(keys)-1 {
					// src.Close()
					return 0, DetachError{}
				}
				nr, er = src.Read(buf)
			}
			nw, ew := dst.Write(preservBuf)
			nr = len(preservBuf)
			if nw > 0 {
				written += int64(nw)
			}
			if ew != nil {
				err = ew
				break
			}
			if nr != nw {
				err = io.ErrShortWrite
				break
			}
		}
		if er != nil {
			if er != io.EOF {
				err = er
			}
			break
		}
	}
	return written, err
}

// WriteGoroutineStacks writes out the goroutine stacks
// of the caller. Up to 32 MB is allocated to print the
// stack.
func WriteGoroutineStacks(w io.Writer) error {
	if w == nil {
		return fmt.Errorf("writer nil")
	}
	buf := make([]byte, 1<<20)
	for i := 0; ; i++ {
		n := runtime.Stack(buf, true)
		if n < len(buf) {
			buf = buf[:n]
			break
		}
		if len(buf) >= 32<<20 {
			break
		}
		buf = make([]byte, 2*len(buf))
	}
	_, err := w.Write(buf)
	return err
}

// WriteGoroutineStacksToFile write goroutine stacks
// to the specified file.
func WriteGoroutineStacksToFile(path string) error {
	f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o666)
	if err != nil {
		return err
	}
	defer f.Close()

	err = WriteGoroutineStacks(f)
	if err != nil {
		return err
	}
	return f.Sync()
}

// GenerateID generates a random unique id.
func GenerateID() (string, error) {
	b := make([]byte, 32)
	if _, err := rand.Read(b); err != nil {
		return "", errors.Wrap(err, "generate ID")
	}
	return hex.EncodeToString(b), nil
}

// openContainerFile opens a file inside a container rootfs safely
func openContainerFile(rootfs, path string) (io.ReadCloser, error) {
	fp, err := securejoin.SecureJoin(rootfs, path)
	if err != nil {
		return nil, err
	}
	return os.Open(fp)
}

// GetUserInfo returns UID, GID and additional groups for specified user
// by looking them up in /etc/passwd and /etc/group
func GetUserInfo(rootfs, userName string) (uid, gid uint32, additionalGids []uint32, _ error) {
	// We don't care if we can't open the file because
	// not all images will have these files
	passwdFile, err := openContainerFile(rootfs, "/etc/passwd")
	if err != nil {
		logrus.Warnf("Failed to open /etc/passwd: %v", err)
	} else {
		defer passwdFile.Close()
	}

	groupFile, err := openContainerFile(rootfs, "/etc/group")
	if err != nil {
		logrus.Warnf("Failed to open /etc/group: %v", err)
	} else {
		defer groupFile.Close()
	}

	execUser, err := user.GetExecUser(userName, nil, passwdFile, groupFile)
	if err != nil {
		return 0, 0, nil, err
	}

	uid = uint32(execUser.Uid)
	gid = uint32(execUser.Gid)
	additionalGids = make([]uint32, 0, len(execUser.Sgids))
	for _, g := range execUser.Sgids {
		additionalGids = append(additionalGids, uint32(g))
	}

	return uid, gid, additionalGids, nil
}

// GeneratePasswd generates a container specific passwd file,
// iff uid is not defined in the containers /etc/passwd
func GeneratePasswd(username string, uid, gid uint32, homedir, rootfs, rundir string) (string, error) {
	// if UID exists inside of container rootfs /etc/passwd then
	// don't generate passwd
	if _, err := lookup.GetUser(rootfs, strconv.Itoa(int(uid))); err == nil {
		return "", nil
	}
	passwdFile := filepath.Join(rundir, "passwd")
	originPasswdFile, err := securejoin.SecureJoin(rootfs, "/etc/passwd")
	if err != nil {
		return "", errors.Wrap(err, "unable to follow symlinks to passwd file")
	}
	info, err := os.Stat(originPasswdFile)
	if err != nil {
		if os.IsNotExist(err) {
			return "", nil
		}
		return "", errors.Wrapf(err, "unable to stat passwd file %s", originPasswdFile)
	}
	// Check if passwd file is world writable
	if info.Mode().Perm()&(0o022) != 0 {
		return "", nil
	}
	passwdUID := info.Sys().(*syscall.Stat_t).Uid
	passwdGID := info.Sys().(*syscall.Stat_t).Gid

	if uid == passwdUID && info.Mode().Perm()&(0o200) != 0 {
		return "", nil
	}

	orig, err := ioutil.ReadFile(originPasswdFile)
	if err != nil {
		// If no /etc/passwd in container ignore and return
		if os.IsNotExist(err) {
			return "", nil
		}
		return "", errors.Wrapf(err, "read passwd file")
	}
	if username == "" {
		username = "default"
	}
	if homedir == "" {
		homedir = "/tmp"
	}
	pwd := fmt.Sprintf("%s%s:x:%d:%d:%s user:%s:/sbin/nologin\n", orig, username, uid, gid, username, homedir)
	if err := ioutil.WriteFile(passwdFile, []byte(pwd), info.Mode()); err != nil {
		return "", errors.Wrap(err, "failed to create temporary passwd file")
	}
	if err := os.Chown(passwdFile, int(passwdUID), int(passwdGID)); err != nil {
		return "", errors.Wrap(err, "failed to chown temporary passwd file")
	}

	return passwdFile, nil
}

// Int32Ptr is a utility function to assign to integer pointer variables
func Int32Ptr(i int32) *int32 {
	return &i
}

// EnsureSaneLogPath is a hack to fix https://issues.k8s.io/44043 which causes
// logPath to be a broken symlink to some magical Docker path. Ideally we
// wouldn't have to deal with this, but until that issue is fixed we have to
// remove the path if it's a broken symlink.
func EnsureSaneLogPath(logPath string) error {
	// If the path exists but the resolved path does not, then we have a broken
	// symlink and we need to remove it.
	fi, err := os.Lstat(logPath)
	if err != nil || fi.Mode()&os.ModeSymlink == 0 {
		// Non-existent files and non-symlinks aren't our problem.
		return nil
	}

	_, err = os.Stat(logPath)
	if os.IsNotExist(err) {
		err = os.RemoveAll(logPath)
		if err != nil {
			return fmt.Errorf("failed to remove bad log path %s: %v", logPath, err)
		}
	}
	return nil
}

func GetLabelOptions(selinuxOptions *types.SELinuxOption) []string {
	labels := []string{}
	if selinuxOptions != nil {
		if selinuxOptions.User != "" {
			labels = append(labels, "user:"+selinuxOptions.User)
		}
		if selinuxOptions.Role != "" {
			labels = append(labels, "role:"+selinuxOptions.Role)
		}
		if selinuxOptions.Type != "" {
			labels = append(labels, "type:"+selinuxOptions.Type)
		}
		if selinuxOptions.Level != "" {
			labels = append(labels, "level:"+selinuxOptions.Level)
		}
	}
	return labels
}

// SyncParent ensures a path's parent directory is synced to disk
func SyncParent(path string) error {
	return Sync(filepath.Dir(path))
}

// Sync ensures a path is synced to disk
func Sync(path string) error {
	f, err := os.OpenFile(path, os.O_RDONLY, 0o755)
	if err != nil {
		return err
	}
	defer f.Close()

	if err := syscall.Fsync(int(f.Fd())); err != nil {
		return err
	}
	return nil
}
<commit_msg>Fix invalid libcontainer GetExecUser call<commit_after>package utils

import (
	"bytes"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"syscall"

	"github.com/containers/podman/v3/pkg/lookup"
	"github.com/cri-o/cri-o/server/cri/types"
	securejoin "github.com/cyphar/filepath-securejoin"
	"github.com/opencontainers/runc/libcontainer/user"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"

	systemdDbus "github.com/coreos/go-systemd/v22/dbus"
	"github.com/godbus/dbus/v5"
)

// ExecCmd executes a command with args and returns its output as a string along
// with an error, if any
func ExecCmd(name string, args ...string) (string, error) {
	cmd := exec.Command(name, args...)
	var stdout bytes.Buffer
	var stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	if v, found := os.LookupEnv("XDG_RUNTIME_DIR"); found {
		cmd.Env = append(cmd.Env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", v))
	}

	err := cmd.Run()
	if err != nil {
		return "", fmt.Errorf("`%v %v` failed: %v %v (%v)", name, strings.Join(args, " "), stderr.String(), stdout.String(), err)
	}

	return stdout.String(), nil
}

// StatusToExitCode converts wait status code to an exit code
func StatusToExitCode(status int) int {
	return ((status) & 0xff00) >> 8
}

// RunUnderSystemdScope adds the specified pid to a systemd scope
func RunUnderSystemdScope(pid int, slice, unitName string, properties ...systemdDbus.Property) error {
	conn, err := systemdDbus.New()
	if err != nil {
		return err
	}
	defaultProperties := []systemdDbus.Property{
		newProp("PIDs", []uint32{uint32(pid)}),
		newProp("Delegate", true),
		newProp("DefaultDependencies", false),
	}
	properties = append(defaultProperties, properties...)
	if slice != "" {
		properties = append(properties, systemdDbus.PropSlice(slice))
	}
	ch := make(chan string)
	_, err = conn.StartTransientUnit(unitName, "replace", properties, ch)
	if err != nil {
		return err
	}
	defer conn.Close()

	// Block until job is started
	<-ch

	return nil
}

func newProp(name string, units interface{}) systemdDbus.Property {
	return systemdDbus.Property{
		Name:  name,
		Value: dbus.MakeVariant(units),
	}
}

// DetachError is special error which returned in case of container detach.
type DetachError struct{}

func (DetachError) Error() string {
	return "detached from container"
}

// CopyDetachable is similar to io.Copy but support a detach key sequence to break out.
func CopyDetachable(dst io.Writer, src io.Reader, keys []byte) (int64, error) {
	var (
		written int64
		err     error
	)
	// Sanity check interfaces
	if dst == nil || src == nil {
		return 0, fmt.Errorf("src/dst reader/writer nil")
	}
	if len(keys) == 0 {
		// Default keys : ctrl-p ctrl-q
		keys = []byte{16, 17}
	}

	buf := make([]byte, 32*1024)
	for {
		nr, er := src.Read(buf)
		if nr > 0 {
			preservBuf := []byte{}
			for i, key := range keys {
				preservBuf = append(preservBuf, buf[0:nr]...)
				if nr != 1 || buf[0] != key {
					break
				}
				if i == len(keys)-1 {
					// src.Close()
					return 0, DetachError{}
				}
				nr, er = src.Read(buf)
			}
			nw, ew := dst.Write(preservBuf)
			nr = len(preservBuf)
			if nw > 0 {
				written += int64(nw)
			}
			if ew != nil {
				err = ew
				break
			}
			if nr != nw {
				err = io.ErrShortWrite
				break
			}
		}
		if er != nil {
			if er != io.EOF {
				err = er
			}
			break
		}
	}
	return written, err
}

// WriteGoroutineStacks writes out the goroutine stacks
// of the caller. Up to 32 MB is allocated to print the
// stack.
func WriteGoroutineStacks(w io.Writer) error {
	if w == nil {
		return fmt.Errorf("writer nil")
	}
	buf := make([]byte, 1<<20)
	for i := 0; ; i++ {
		n := runtime.Stack(buf, true)
		if n < len(buf) {
			buf = buf[:n]
			break
		}
		if len(buf) >= 32<<20 {
			break
		}
		buf = make([]byte, 2*len(buf))
	}
	_, err := w.Write(buf)
	return err
}

// WriteGoroutineStacksToFile write goroutine stacks
// to the specified file.
func WriteGoroutineStacksToFile(path string) error {
	f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o666)
	if err != nil {
		return err
	}
	defer f.Close()

	err = WriteGoroutineStacks(f)
	if err != nil {
		return err
	}
	return f.Sync()
}

// GenerateID generates a random unique id.
func GenerateID() (string, error) {
	b := make([]byte, 32)
	if _, err := rand.Read(b); err != nil {
		return "", errors.Wrap(err, "generate ID")
	}
	return hex.EncodeToString(b), nil
}

// openContainerFile opens a file inside a container rootfs safely
func openContainerFile(rootfs, path string) (io.ReadCloser, error) {
	fp, err := securejoin.SecureJoin(rootfs, path)
	if err != nil {
		return nil, err
	}
	fh, err := os.Open(fp)
	if err != nil {
		// This is needed because a nil *os.File is different to a nil
		// io.ReadCloser and this causes GetExecUser to not detect that the
		// container file is missing.
		return nil, err
	}
	return fh, nil
}

// GetUserInfo returns UID, GID and additional groups for specified user
// by looking them up in /etc/passwd and /etc/group
func GetUserInfo(rootfs, userName string) (uid, gid uint32, additionalGids []uint32, _ error) {
	// We don't care if we can't open the file because
	// not all images will have these files
	passwdFile, err := openContainerFile(rootfs, "/etc/passwd")
	if err != nil {
		logrus.Warnf("Failed to open /etc/passwd: %v", err)
	} else {
		defer passwdFile.Close()
	}

	groupFile, err := openContainerFile(rootfs, "/etc/group")
	if err != nil {
		logrus.Warnf("Failed to open /etc/group: %v", err)
	} else {
		defer groupFile.Close()
	}

	execUser, err := user.GetExecUser(userName, nil, passwdFile, groupFile)
	if err != nil {
		return 0, 0, nil, errors.Wrap(err, "get exec user")
	}

	uid = uint32(execUser.Uid)
	gid = uint32(execUser.Gid)
	additionalGids = make([]uint32, 0, len(execUser.Sgids))
	for _, g := range execUser.Sgids {
		additionalGids = append(additionalGids, uint32(g))
	}

	return uid, gid, additionalGids, nil
}

// GeneratePasswd generates a container specific passwd file,
// iff uid is not defined in the containers /etc/passwd
func GeneratePasswd(username string, uid, gid uint32, homedir, rootfs, rundir string) (string, error) {
	// if UID exists inside of container rootfs /etc/passwd then
	// don't generate passwd
	if _, err := lookup.GetUser(rootfs, strconv.Itoa(int(uid))); err == nil {
		return "", nil
	}
	passwdFile := filepath.Join(rundir, "passwd")
	originPasswdFile, err := securejoin.SecureJoin(rootfs, "/etc/passwd")
	if err != nil {
		return "", errors.Wrap(err, "unable to follow symlinks to passwd file")
	}
	info, err := os.Stat(originPasswdFile)
	if err != nil {
		if os.IsNotExist(err) {
			return "", nil
		}
		return "", errors.Wrapf(err, "unable to stat passwd file %s", originPasswdFile)
	}
	// Check if passwd file is world writable
	if info.Mode().Perm()&(0o022) != 0 {
		return "", nil
	}
	passwdUID := info.Sys().(*syscall.Stat_t).Uid
	passwdGID := info.Sys().(*syscall.Stat_t).Gid

	if uid == passwdUID && info.Mode().Perm()&(0o200) != 0 {
		return "", nil
	}

	orig, err := ioutil.ReadFile(originPasswdFile)
	if err != nil {
		// If no /etc/passwd in container ignore and return
		if os.IsNotExist(err) {
			return "", nil
		}
		return "", errors.Wrapf(err, "read passwd file")
	}
	if username == "" {
		username = "default"
	}
	if homedir == "" {
		homedir = "/tmp"
	}
	pwd := fmt.Sprintf("%s%s:x:%d:%d:%s user:%s:/sbin/nologin\n", orig, username, uid, gid, username, homedir)
	if err := ioutil.WriteFile(passwdFile, []byte(pwd), info.Mode()); err != nil {
		return "", errors.Wrap(err, "failed to create temporary passwd file")
	}
	if err := os.Chown(passwdFile, int(passwdUID), int(passwdGID)); err != nil {
		return "", errors.Wrap(err, "failed to chown temporary passwd file")
	}

	return passwdFile, nil
}

// Int32Ptr is a utility function to assign to integer pointer variables
func Int32Ptr(i int32) *int32 {
	return &i
}

// EnsureSaneLogPath is a hack to fix https://issues.k8s.io/44043 which causes
// logPath to be a broken symlink to some magical Docker path. Ideally we
// wouldn't have to deal with this, but until that issue is fixed we have to
// remove the path if it's a broken symlink.
func EnsureSaneLogPath(logPath string) error {
	// If the path exists but the resolved path does not, then we have a broken
	// symlink and we need to remove it.
	fi, err := os.Lstat(logPath)
	if err != nil || fi.Mode()&os.ModeSymlink == 0 {
		// Non-existent files and non-symlinks aren't our problem.
		return nil
	}

	_, err = os.Stat(logPath)
	if os.IsNotExist(err) {
		err = os.RemoveAll(logPath)
		if err != nil {
			return fmt.Errorf("failed to remove bad log path %s: %v", logPath, err)
		}
	}
	return nil
}

func GetLabelOptions(selinuxOptions *types.SELinuxOption) []string {
	labels := []string{}
	if selinuxOptions != nil {
		if selinuxOptions.User != "" {
			labels = append(labels, "user:"+selinuxOptions.User)
		}
		if selinuxOptions.Role != "" {
			labels = append(labels, "role:"+selinuxOptions.Role)
		}
		if selinuxOptions.Type != "" {
			labels = append(labels, "type:"+selinuxOptions.Type)
		}
		if selinuxOptions.Level != "" {
			labels = append(labels, "level:"+selinuxOptions.Level)
		}
	}
	return labels
}

// SyncParent ensures a path's parent directory is synced to disk
func SyncParent(path string) error {
	return Sync(filepath.Dir(path))
}

// Sync ensures a path is synced to disk
func Sync(path string) error {
	f, err := os.OpenFile(path, os.O_RDONLY, 0o755)
	if err != nil {
		return err
	}
	defer f.Close()

	if err := syscall.Fsync(int(f.Fd())); err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package utils

import (
	"encoding/json"
	"fmt"
	"github.com/dustin/go-humanize"
	"github.com/gost-c/gost-cli/colors"
	"github.com/mitchellh/go-homedir"
	"io/ioutil"
	"net/http"
	"os"
	"path"
	"strings"
)

var (
	// BaseURL is the base url of gost api services
	BaseURL = "https://iuao0sjxmi.execute-api.ap-southeast-1.amazonaws.com/development/"
	//BaseURL = "http://localhost:9393/"

	// WebURL is the base url of gost web services
	WebURL = "https://gost.zcong.moe/#/gost/"
	space  = "  "
	// MaxSize is single file max size, 10k
	MaxSize = int64(1024 * 10)
	// MaxSizeHuman is max size for human
	MaxSizeHuman = humanize.Bytes(uint64(MaxSize))
	// MaxFilesCount is max files count allowed
	MaxFilesCount = 10
	// ErrMaxFilesCount is error message for too many files
	ErrMaxFilesCount = fmt.Errorf("More than %d files is not allowed ", MaxFilesCount)
)

// PathStat is file stat
type PathStat struct {
	// Error is error message
	Error error
	// IsFolder show if the PathStat is folder
	IsFolder bool
	// Size is file humanized size if is a file
	Size string
}

// ConfigFile is the true path of config file `.gostrc`
var ConfigFile string

const config = ".gostrc"

func init() {
	home, err := homedir.Dir()
	if err != nil {
		Fail("An error occurred when get user home!")
		os.Exit(1)
	}
	ConfigFile = path.Join(home, config)
}

// WriteConfig write token to config file
func WriteConfig(token []byte) error {
	return ioutil.WriteFile(ConfigFile, token, 0644)
}

// ReadConfig read token from config file
func ReadConfig() ([]byte, error) {
	return ioutil.ReadFile(ConfigFile)
}

// Success log success message with colors
func Success(str string) {
	fmt.Println()
	fmt.Printf("%s%s%s", colors.Green("SUCCESS"), space, str)
	fmt.Println()
}

// Fail log error message with colors
func Fail(str string) {
	fmt.Println()
	fmt.Printf("%s%s%s", colors.Red("ERROR"), space, str)
	fmt.Println()
}

// DoRequest do a http request and decode the json response
func DoRequest(method, url string, v interface{}, headers map[string]string) error {
	client := &http.Client{}
	req, err := http.NewRequest(method, url, strings.NewReader(""))
	if err != nil {
		return err
	}
	for k, v := range headers {
		req.Header.Set(k, v)
	}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	err = json.NewDecoder(resp.Body).Decode(v)
	return err
}

// GetPathStat return the path type, if is file return file size
func GetPathStat(p string) *PathStat {
	fileInfo, err := os.Stat(p)
	ps := &PathStat{}
	if err != nil {
		ps.Error = err
		return ps
	}
	if fileInfo.IsDir() {
		ps.IsFolder = true
		return ps
	}
	s := fileInfo.Size()
	if s > MaxSize {
		ps.Error = errFileTooBig(s)
		return ps
	}
	ps.IsFolder = false
	ps.Size = humanize.Bytes(uint64(fileInfo.Size()))
	return ps
}

func errFileTooBig(s int64) error {
	return fmt.Errorf("File is too big %s, max allowed size is %s ", humanize.Bytes(uint64(s)), MaxSizeHuman)
}

// Pad helper.
func Pad() func() {
	println()
	return func() {
		println()
	}
}

// LogPad outputs a log message with padding.
func LogPad(msg string) {
	defer Pad()()
	fmt.Println(msg)
}
<commit_msg>tweak: debug friendly<commit_after>package utils

import (
	"encoding/json"
	"fmt"
	"github.com/dustin/go-humanize"
	"github.com/gost-c/gost-cli/colors"
	"github.com/mitchellh/go-homedir"
	"io/ioutil"
	"net/http"
	"os"
	"path"
	"strings"
)

var (
	// BaseURL is the base url of gost api services
	BaseURL = GetEnvOrDefault("GOSTBASEURL", "https://iuao0sjxmi.execute-api.ap-southeast-1.amazonaws.com/development/")
	// WebURL is the base url of gost web services
	WebURL = GetEnvOrDefault("GOSTWEBURL", "https://gost.zcong.moe/#/gost/")
	space  = "  "
	// MaxSize is single file max size, 10k
	MaxSize = int64(1024 * 10)
	// MaxSizeHuman is max size for human
	MaxSizeHuman = humanize.Bytes(uint64(MaxSize))
	// MaxFilesCount is max files count allowed
	MaxFilesCount = 10
	// ErrMaxFilesCount is error message for too many files
	ErrMaxFilesCount = fmt.Errorf("More than %d files is not allowed ", MaxFilesCount)
)

// PathStat is file stat
type PathStat struct {
	// Error is error message
	Error error
	// IsFolder show if the PathStat is folder
	IsFolder bool
	// Size is file humanized size if is a file
	Size string
}

// ConfigFile is the true path of config file `.gostrc`
var ConfigFile string

const config = ".gostrc"

func init() {
	home, err := homedir.Dir()
	if err != nil {
		Fail("An error occurred when get user home!")
		os.Exit(1)
	}
	ConfigFile = path.Join(home, config)
}

// GetEnvOrDefault return a env value or a default if not exists
func GetEnvOrDefault(key, d string) string {
	v := os.Getenv(key)
	if v == "" {
		return d
	}
	return v
}

// WriteConfig write token to config file
func WriteConfig(token []byte) error {
	return ioutil.WriteFile(ConfigFile, token, 0644)
}

// ReadConfig read token from config file
func ReadConfig() ([]byte, error) {
	return ioutil.ReadFile(ConfigFile)
}

// Success log success message with colors
func Success(str string) {
	fmt.Println()
	fmt.Printf("%s%s%s", colors.Green("SUCCESS"), space, str)
	fmt.Println()
}

// Fail log error message with colors
func Fail(str string) {
	fmt.Println()
	fmt.Printf("%s%s%s", colors.Red("ERROR"), space, str)
	fmt.Println()
}

// DoRequest do a http request and decode the json response
func DoRequest(method, url string, v interface{}, headers map[string]string) error {
	client := &http.Client{}
	req, err := http.NewRequest(method, url, strings.NewReader(""))
	if err != nil {
		return err
	}
	for k, v := range headers {
		req.Header.Set(k, v)
	}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	err = json.NewDecoder(resp.Body).Decode(v)
	return err
}

// GetPathStat return the path type, if is file return file size
func GetPathStat(p string) *PathStat {
	fileInfo, err := os.Stat(p)
	ps := &PathStat{}
	if err != nil {
		ps.Error = err
		return ps
	}
	if fileInfo.IsDir() {
		ps.IsFolder = true
		return ps
	}
	s := fileInfo.Size()
	if s > MaxSize {
		ps.Error = errFileTooBig(s)
		return ps
	}
	ps.IsFolder = false
	ps.Size = humanize.Bytes(uint64(fileInfo.Size()))
	return ps
}

func errFileTooBig(s int64) error {
	return fmt.Errorf("File is too big %s, max allowed size is %s ", humanize.Bytes(uint64(s)), MaxSizeHuman)
}

// Pad helper.
func Pad() func() {
	println()
	return func() {
		println()
	}
}

// LogPad outputs a log message with padding.
func LogPad(msg string) {
	defer Pad()()
	fmt.Println(msg)
}
<|endoftext|>


<commit_before>package utils

import (
	"fmt"
	"os"
	"image"
	"image/png"
	"image/color"
)

// ReadStdin reads a png file from standard input.
func ReadStdin() image.Image {
	if s,_ := os.Stdin.Stat(); s.Size() == 0 {
		fmt.Fprintln(os.Stderr, "Error: need to provide image by STDIN. See \"img help\".")
		os.Exit(1)
	}
	img, _ := png.Decode(os.Stdin)

	return img
}

// WriteStdout writes an Image to standard output.
func WriteStdout(img image.Image) {
	png.Encode(os.Stdout, img)
}

// Warn prints a message to standard error
func Warn(s interface{}) {
	fmt.Fprintln(os.Stderr, s)
}

// NormalisedRGBA returns the RGBA colour channel values of a Color in a
// normalised form.
func NormalisedRGBA(c color.Color) (rn, gn, bn, an uint32) {
	d := color.NRGBAModel.Convert(c).(color.NRGBA)
	r := d.R; g := d.G; b := d.B; a := d.A

	// Need to do some crazy type conversions first
	rn = uint32(uint8(r))
	gn = uint32(uint8(g))
	bn = uint32(uint8(b))
	an = uint32(uint8(a))

	return
}

// NormalisedRGBAf returns the RGBA colour channel values as floating point
// numbers with values from 0 to 255.
func NormalisedRGBAf(c color.Color) (rn, gn, bn, an float64) {
	r, g, b, a := NormalisedRGBA(c)

	rn = float64(r)
	gn = float64(g)
	bn = float64(b)
	an = float64(a)

	return
}

// RatioRGBA returns the RGBA colour channel values as floating point numbers
// with values from 0 to 1.
func RatioRGBA(c color.Color) (rn, gn, bn, an float64) {
	r, g, b, a := NormalisedRGBAf(c)

	rn = r / 255
	gn = g / 255
	bn = b / 255
	an = a / 255

	return
}

// Truncate takes a colour channel value and forces it into the range 0 to 255
// by setting any value below 0 to 0 and and any above 255 to 255.
func Truncate(n uint32) uint32 {
	if n < 0 { return 0 } else if n > 255 { return 255 }
	return n
}

// Truanctef is identical to Truncate but takes and returns a float64.
func Truncatef(n float64) float64 {
	if n < 0 { return 0 } else if n > 255 { return 255 }
	return n
}

// Closeness calculates the "closeness" of two colours by finding the sum of
// differences in each colour channel.
func Closeness(one, two color.Color) uint32 {
	a, b, c, d := NormalisedRGBA(one)
	w, x, y, z := NormalisedRGBA(two)

	return (a - w) + (b - x) + (c - y) + (d - z)
}

// Average takes a list of colours and returns the average.
func Average(cs... color.Color) color.Color {
	var red, green, blue, alpha uint32
	red = 0; green = 0; blue = 0; alpha = 0

	for i := 0; i < len(cs); i++ {
		r, g, b, a := NormalisedRGBA(cs[i])

		red += r; green += g; blue += b; alpha += a
	}

	return color.NRGBA{
		uint8(red   / uint32(len(cs))),
		uint8(green / uint32(len(cs))),
		uint8(blue  / uint32(len(cs))),
		uint8(alpha / uint32(len(cs))),
	}
}

// Min returns the smallest value in the list of uint32s given.
func Min(ns... uint32) (n uint32) {
	if len(ns) > 0 {
		n = ns[0]
	}
	for i := 1; i < len(ns); i++ {
		if ns[i] < n {
			n = ns[i]
		}
	}
	return
}

// Minf returns the smallest value in the list of float64s given.
func Minf(ns... float64) (n float64) {
	if len(ns) > 0 {
		n = ns[0]
	}
	for i := 1; i < len(ns); i++ {
		if ns[i] < n {
			n = ns[i]
		}
	}
	return
}

// Max returns the largest value in the list of uint32s given.
func Max(ns... uint32) (n uint32) {
	if len(ns) > 0 {
		n = ns[0]
	}
	for i := 1; i < len(ns); i++ {
		if ns[i] > n {
			n = ns[i]
		}
	}
	return
}

// Maxf returns the largest value in the list of float64s given.
func Maxf(ns... float64) (n float64) {
	if len(ns) > 0 {
		n = ns[0]
	}
	for i := 1; i < len(ns); i++ {
		if ns[i] > n {
			n = ns[i]
		}
	}
	return
}

// EachPixel iterates through each pixel of the Image applies the function given
// and draws the result to a new Image which is then returned.
func EachPixel(img image.Image, f func(c color.Color) color.Color) image.Image {
	b := img.Bounds()
	o := image.NewRGBA(image.Rect(0, 0, b.Dx(), b.Dy()))

	for y := b.Min.Y; y < b.Max.Y; y++ {
		for x := b.Min.X; x < b.Max.X; x++ {
			result := f(img.At(x, y))
			o.Set(x, y, result)
		}
	}

	return o
}
<commit_msg>Removed STDIN existence warning as it was broken<commit_after>package utils

import (
	"fmt"
	"os"
	"image"
	"image/png"
	"image/color"
)

// ReadStdin reads a png file from standard input.
func ReadStdin() image.Image {
	img, _ := png.Decode(os.Stdin)

	return img
}

// WriteStdout writes an Image to standard output.
func WriteStdout(img image.Image) {
	png.Encode(os.Stdout, img)
}

// Warn prints a message to standard error
func Warn(s interface{}) {
	fmt.Fprintln(os.Stderr, s)
}

// NormalisedRGBA returns the RGBA colour channel values of a Color in a
// normalised form.
func NormalisedRGBA(c color.Color) (rn, gn, bn, an uint32) {
	d := color.NRGBAModel.Convert(c).(color.NRGBA)
	r := d.R; g := d.G; b := d.B; a := d.A

	// Need to do some crazy type conversions first
	rn = uint32(uint8(r))
	gn = uint32(uint8(g))
	bn = uint32(uint8(b))
	an = uint32(uint8(a))

	return
}

// NormalisedRGBAf returns the RGBA colour channel values as floating point
// numbers with values from 0 to 255.
func NormalisedRGBAf(c color.Color) (rn, gn, bn, an float64) {
	r, g, b, a := NormalisedRGBA(c)

	rn = float64(r)
	gn = float64(g)
	bn = float64(b)
	an = float64(a)

	return
}

// RatioRGBA returns the RGBA colour channel values as floating point numbers
// with values from 0 to 1.
func RatioRGBA(c color.Color) (rn, gn, bn, an float64) {
	r, g, b, a := NormalisedRGBAf(c)

	rn = r / 255
	gn = g / 255
	bn = b / 255
	an = a / 255

	return
}

// Truncate takes a colour channel value and forces it into the range 0 to 255
// by setting any value below 0 to 0 and and any above 255 to 255.
func Truncate(n uint32) uint32 {
	if n < 0 { return 0 } else if n > 255 { return 255 }
	return n
}

// Truanctef is identical to Truncate but takes and returns a float64.
func Truncatef(n float64) float64 {
	if n < 0 { return 0 } else if n > 255 { return 255 }
	return n
}

// Closeness calculates the "closeness" of two colours by finding the sum of
// differences in each colour channel.
func Closeness(one, two color.Color) uint32 {
	a, b, c, d := NormalisedRGBA(one)
	w, x, y, z := NormalisedRGBA(two)

	return (a - w) + (b - x) + (c - y) + (d - z)
}

// Average takes a list of colours and returns the average.
func Average(cs... color.Color) color.Color {
	var red, green, blue, alpha uint32
	red = 0; green = 0; blue = 0; alpha = 0

	for i := 0; i < len(cs); i++ {
		r, g, b, a := NormalisedRGBA(cs[i])

		red += r; green += g; blue += b; alpha += a
	}

	return color.NRGBA{
		uint8(red   / uint32(len(cs))),
		uint8(green / uint32(len(cs))),
		uint8(blue  / uint32(len(cs))),
		uint8(alpha / uint32(len(cs))),
	}
}

// Min returns the smallest value in the list of uint32s given.
func Min(ns... uint32) (n uint32) {
	if len(ns) > 0 {
		n = ns[0]
	}
	for i := 1; i < len(ns); i++ {
		if ns[i] < n {
			n = ns[i]
		}
	}
	return
}

// Minf returns the smallest value in the list of float64s given.
func Minf(ns... float64) (n float64) {
	if len(ns) > 0 {
		n = ns[0]
	}
	for i := 1; i < len(ns); i++ {
		if ns[i] < n {
			n = ns[i]
		}
	}
	return
}

// Max returns the largest value in the list of uint32s given.
func Max(ns... uint32) (n uint32) {
	if len(ns) > 0 {
		n = ns[0]
	}
	for i := 1; i < len(ns); i++ {
		if ns[i] > n {
			n = ns[i]
		}
	}
	return
}

// Maxf returns the largest value in the list of float64s given.
func Maxf(ns... float64) (n float64) {
	if len(ns) > 0 {
		n = ns[0]
	}
	for i := 1; i < len(ns); i++ {
		if ns[i] > n {
			n = ns[i]
		}
	}
	return
}

// EachPixel iterates through each pixel of the Image applies the function given
// and draws the result to a new Image which is then returned.
func EachPixel(img image.Image, f func(c color.Color) color.Color) image.Image {
	b := img.Bounds()
	o := image.NewRGBA(image.Rect(0, 0, b.Dx(), b.Dy()))

	for y := b.Min.Y; y < b.Max.Y; y++ {
		for x := b.Min.X; x < b.Max.X; x++ {
			result := f(img.At(x, y))
			o.Set(x, y, result)
		}
	}

	return o
}
<|endoftext|>


<commit_before>// Package colonycore define the struct that needed by other package in colony
package colonycore

import (
	/*"github.com/eaciit/errorlib"
		// "github.com/eaciit/orm/v1"
		// "log"
		"mime/multipart"
		"strconv"
		"strings"
	    "github.com/eaciit/toolkit"
	*/
	"github.com/eaciit/orm/v1"
	tk "github.com/eaciit/toolkit"
	"time"
)

const (
	ACTION_TYPE_HIVE     = "HIVE"
	ACTION_TYPE_HDFS     = "HDFS"
	ACTION_TYPE_SPARK    = "SPARK"
	ACTION_TYPE_DECISION = "DECISION"
	ACTION_TYPE_SSH      = "SSH"
	ACTION_TYPE_KAFKA    = "KAFKA"

	FORK_TYPE_ALL       = "ALL"
	FORK_TYPE_ONE       = "ONE"
	FORK_TYPE_MANDATORY = "MANDATORY"

	SSH_OPERATION_MKDIR = "MKDIR"
	// SSH_OPERATION_* please define
)

// DataFlow to define the flow name and description
// and also have the list of the actions inside the flow
// Actions list of action, the content of the action can be FlowAction or "list of FlowAction" -> for fork action
type DataFlow struct {
	orm.ModelBase
	ID          string       `json:"_id",bson:"_id"`
	Name        string       `json:"name"`
	Description string       `json:"description"`
	CreatedDate time.Time    `json:"createddate"`
	CreatedBy   string       `json:"createdby"`
	Actions     []FlowAction `json:"actions"`
	DataShapes  tk.M         `json:"datashapes"`
}

// FlowAction define the action that exist
// Type refer to cons ACTION_TYPE_*
// Action define the type of action e.g. Spark, HDFS, etc
// Server define which server for the action
// OK next step to go if the result is OK
// KO next step to go if the result is KO or NOT OK or ERROR
// Retry how many time the system will do the process if ERROR happen and then go to KO condition, default will be 3
// Wait interval in minute, default will be 1
type FlowAction struct {
	Id          string      `json:"_id"`
	Name        string      `json:"name"`
	Description string      `json:"description"`
	Type        string      `json:"type"`
	Server      Server      `json:"server"`
	Action      interface{} `json:"action"`
	OK          []string    `json:"ok"`
	KO          []string    `json:"ko"`
	Retry       int         `json:"retry"`
	Interval    int         `json:"interval"`
	FirstAction bool
}

// ActionHive action for HIVE
// ScriptPath path of the hive script to run
type ActionHive struct {
	ScriptPath string
	Params     []string
}

// ActionHDFS action for HDFS type for server
// Operation type operation
// The HDFS connection will be provided by the server from FlowAction
// Path can be used for the current path, or old path, also can included the filename
// NewPath can be used for move, copy, etc
// Permission e.g. rwx-r--r-
type ActionHDFS struct {
	Operation  string `json:"operation"`
	Path       string `json:"path"`
	NewPath    string `json:"newpath"`
	Permission string
	User       string
	Group      string
}

// ActionSSH action for SSH type of server
// Operation type operation
// The ssh connection will be provided by the server from FlowAction
// Path can be used for the current path, or old path, also can included the filename
// NewPath can be used for move, copy, etc
// Permission e.g. rwx-r--r-
type ActionSSH struct {
	Operation  string `json:"operation"`
	Path       string `json:"path"`
	NewPath    string `json:"newpath"`
	Permission string
	User       string
	Group      string
}

// ActionSpark action for SPARK
// Type of the code upload e.g. Java, Scala
// Mode e.g. client, cluster
// Args for other arguments, e.g. --executor-memory 2G
// Application the spark code will be mantain as application
type ActionSpark struct {
	Type        string `json: "type"`
	Master      string
	Mode        string
	AppName     string
	Args        []string
	Application Application
}

// ActionSpark action for Application
// Type e.g. Java, Scala, GO
// Application refer to application page
// when creating the action with type = application then should follow the application screen standard
type ActionApplication struct {
	Type        string `json: "type"`
	Application Application
}

// ActionHadoopStreaming for hadoop streaming
// Mapper name
// Reducer name
// Input can be folder or file
// Output folder
// Files list of file inside the hdfs
// the server type should be hdfs
type ActionHadoopStreaming struct {
	Mapper  string
	Reducer string
	Input   string
	Output  string
	Files   []string
}

// ActionKafka action for KAFKA
type ActionKafka struct {
	// other field will be different for each action type
}

/*
Doesn't need the Queestion, can be achived by using OK and KO or using decision
type ActionQuestion struct {
	Question string `json:"question"`
	Yes      string `json:"yes"`
	No       string `json:"no"`
	// other field will be different for each action type
}*/

// ActionDecision action to define the list of decision that action have
type ActionDecision struct {
	Conditions []Condition `json:"conditions"`
	// other field will be different for each action type
}

// Condition condition for the action decision
type Condition struct {
	Result     string `json:"result"`
	FlowAction string `json:"flowaction"`
}

// ActionFork to define the forking condition
// Type refer to cons FORK_TYPE_*
type ActionFork struct {
	Actions []FlowAction `json:"actions"`
	Type    string       `json: "type"`
}

type DataFlowProcess struct {
	Id          string `json:"_id"`
	Flow        DataFlow
	Steps       []FlowAction
	StartDate   time.Time
	EndDate     time.Time
	UserStarted string
}

func (c *DataFlow) TableName() string {
	return "dataflow"
}

func (c *DataFlow) RecordID() interface{} {
	return c.ID
}
<commit_msg>data flow model<commit_after>// Package colonycore define the struct that needed by other package in colony
package colonycore

import (
	/*"github.com/eaciit/errorlib"
		// "github.com/eaciit/orm/v1"
		// "log"
		"mime/multipart"
		"strconv"
		"strings"
	    "github.com/eaciit/toolkit"
	*/
	"github.com/eaciit/orm/v1"
	tk "github.com/eaciit/toolkit"
	"time"
)

const (
	ACTION_TYPE_HIVE     = "HIVE"
	ACTION_TYPE_HDFS     = "HDFS"
	ACTION_TYPE_SPARK    = "SPARK"
	ACTION_TYPE_DECISION = "DECISION"
	ACTION_TYPE_SSH      = "SSH"
	ACTION_TYPE_KAFKA    = "KAFKA"

	FORK_TYPE_ALL       = "ALL"
	FORK_TYPE_ONE       = "ONE"
	FORK_TYPE_MANDATORY = "MANDATORY"

	SSH_OPERATION_MKDIR = "MKDIR"
	// SSH_OPERATION_* please define
)

// DataFlow to define the flow name and description
// and also have the list of the actions inside the flow
// Actions list of action, the content of the action can be FlowAction or "list of FlowAction" -> for fork action
type DataFlow struct {
	orm.ModelBase
	ID           string       `json:"_id",bson:"_id"`
	Name         string       `json:"name"`
	Description  string       `json:"description"`
	CreatedDate  time.Time    `json:"createddate"`
	LastModified time.Time    `json:"lastmodified"`
	CreatedBy    string       `json:"createdby"`
	Actions      []FlowAction `json:"actions"`
	DataShapes   tk.M         `json:"datashapes"`
}

// FlowAction define the action that exist
// Type refer to cons ACTION_TYPE_*
// Action define the type of action e.g. Spark, HDFS, etc
// Server define which server for the action
// OK next step to go if the result is OK
// KO next step to go if the result is KO or NOT OK or ERROR
// Retry how many time the system will do the process if ERROR happen and then go to KO condition, default will be 3
// Wait interval in minute, default will be 1
type FlowAction struct {
	Id          string      `json:"_id"`
	Name        string      `json:"name"`
	Description string      `json:"description"`
	Type        string      `json:"type"`
	Server      Server      `json:"server"`
	Action      interface{} `json:"action"`
	OK          []string    `json:"ok"`
	KO          []string    `json:"ko"`
	Retry       int         `json:"retry"`
	Interval    int         `json:"interval"`
	FirstAction bool
}

// ActionHive action for HIVE
// ScriptPath path of the hive script to run
type ActionHive struct {
	ScriptPath string
	Params     []string
}

// ActionHDFS action for HDFS type for server
// Operation type operation
// The HDFS connection will be provided by the server from FlowAction
// Path can be used for the current path, or old path, also can included the filename
// NewPath can be used for move, copy, etc
// Permission e.g. rwx-r--r-
type ActionHDFS struct {
	Operation  string `json:"operation"`
	Path       string `json:"path"`
	NewPath    string `json:"newpath"`
	Permission string
	User       string
	Group      string
}

// ActionSSH action for SSH type of server
// Operation type operation
// The ssh connection will be provided by the server from FlowAction
// Path can be used for the current path, or old path, also can included the filename
// NewPath can be used for move, copy, etc
// Permission e.g. rwx-r--r-
type ActionSSH struct {
	Operation  string `json:"operation"`
	Path       string `json:"path"`
	NewPath    string `json:"newpath"`
	Permission string
	User       string
	Group      string
}

// ActionSpark action for SPARK
// Type of the code upload e.g. Java, Scala
// Mode e.g. client, cluster
// Args for other arguments, e.g. --executor-memory 2G
// Application the spark code will be mantain as application
type ActionSpark struct {
	Type        string `json: "type"`
	Master      string
	Mode        string
	AppName     string
	Args        []string
	Application Application
}

// ActionSpark action for Application
// Type e.g. Java, Scala, GO
// Application refer to application page
// when creating the action with type = application then should follow the application screen standard
type ActionApplication struct {
	Type        string `json: "type"`
	Application Application
}

// ActionHadoopStreaming for hadoop streaming
// Mapper name
// Reducer name
// Input can be folder or file
// Output folder
// Files list of file inside the hdfs
// the server type should be hdfs
type ActionHadoopStreaming struct {
	Mapper  string
	Reducer string
	Input   string
	Output  string
	Files   []string
}

// ActionKafka action for KAFKA
type ActionKafka struct {
	// other field will be different for each action type
}

/*
Doesn't need the Queestion, can be achived by using OK and KO or using decision
type ActionQuestion struct {
	Question string `json:"question"`
	Yes      string `json:"yes"`
	No       string `json:"no"`
	// other field will be different for each action type
}*/

// ActionDecision action to define the list of decision that action have
type ActionDecision struct {
	Conditions []Condition `json:"conditions"`
	// other field will be different for each action type
}

// Condition condition for the action decision
type Condition struct {
	Result     string `json:"result"`
	FlowAction string `json:"flowaction"`
}

// ActionFork to define the forking condition
// Type refer to cons FORK_TYPE_*
type ActionFork struct {
	Actions []FlowAction `json:"actions"`
	Type    string       `json: "type"`
}

type DataFlowProcess struct {
	Id          string `json:"_id"`
	Flow        DataFlow
	Steps       []FlowAction
	StartDate   time.Time
	EndDate     time.Time
	UserStarted string
}

func (c *DataFlow) TableName() string {
	return "dataflow"
}

func (c *DataFlow) RecordID() interface{} {
	return c.ID
}
<|endoftext|>


<commit_before>package terp

import (
	. "fmt"
	"log"
	"net/http"
	"strconv"
)

var _ = log.Printf
var _ = strconv.ParseFloat

var TBuiltins map[string]TCommand = make(map[string]TCommand, 0)

func (fr *Frame) initTBuiltins() {
	TBuiltins["+"] = MkChainingBinaryFlopTCmd(fr, 0.0, func(a, b float64) float64 { return a + b })
	TBuiltins["*"] = MkChainingBinaryFlopTCmd(fr, 1.0, func(a, b float64) float64 { return a * b })
	TBuiltins["-"] = MkBinaryFlopTCmd(fr, func(a, b float64) float64 { return a - b })
	TBuiltins["/"] = MkBinaryFlopTCmd(fr, func(a, b float64) float64 { return a / b })

	TBuiltins["=="] = MkBinaryFlopBoolTCmd(fr, func(a, b float64) bool { return (a == b) })
	TBuiltins["!="] = MkBinaryFlopBoolTCmd(fr, func(a, b float64) bool { return (a != b) })
	TBuiltins["<"] = MkBinaryFlopBoolTCmd(fr, func(a, b float64) bool { return (a < b) })
	TBuiltins["<="] = MkBinaryFlopBoolTCmd(fr, func(a, b float64) bool { return (a <= b) })
	TBuiltins[">"] = MkBinaryFlopBoolTCmd(fr, func(a, b float64) bool { return (a > b) })
	TBuiltins[">="] = MkBinaryFlopBoolTCmd(fr, func(a, b float64) bool { return (a >= b) })
	TBuiltins["must"] = tcmdMust

	TBuiltins["if"] = tcmdIf
	TBuiltins["get"] = tcmdGet
	TBuiltins["set"] = tcmdSet
	TBuiltins["puts"] = tcmdPuts
	TBuiltins["proc"] = newcmd(cmdProc)
	TBuiltins["ls"] = newcmd(cmdLs)
	TBuiltins["slen"] = newcmd(cmdSLen)
	TBuiltins["llen"] = newcmd(cmdLLen)
	TBuiltins["list"] = newcmd(cmdList)
	TBuiltins["sat"] = tcmdSAt  // a.k.a. string index
	TBuiltins["lat"] = tcmdLAt  // a.k.a. lindex
	TBuiltins["nil"] = newcmd(cmdNil)
	TBuiltins["http_handler"] = newcmd(cmdHttpHandler)
}
type BinaryFlop func(a, b float64) float64
type BinaryFlopBool func(a, b float64) bool

func MkBinaryFlopTCmd(fr *Frame, flop BinaryFlop) TCommand {
	return func(fr *Frame, argv []T) T {
		a, b := TArgv2(argv)
		return MkTf(flop(a.Float(), b.Float()))
	}
}

func MkBinaryFlopBoolTCmd(fr *Frame, flop BinaryFlopBool) TCommand {
	return func(fr *Frame, argv []T) T {
		a, b := TArgv2(argv)
		return MkTb(flop(a.Float(), b.Float()))
	}
}

func MkChainingBinaryFlopTCmd(fr *Frame, starter float64, flop BinaryFlop) TCommand {
	return func(fr *Frame, argv []T) T {
		z := starter // Be sure not to modify starter!  It is captured.
		for _, a := range argv[1:] {
			z = flop(z, a.Float())
		}
		return MkTf(z)
	}
}

func TTruth(a T) bool {
	switch x := a.(type) {
	case Tf:
		return x.f != 0
	case Ts:
		return len(x.s) > 0
	case Tl:
		return len(x.l) > 0
	}
	// To Do: Value(nil) Value(false) are false.
	return true
}
func Truth(a Any) bool {
	switch x := a.(type) {
	case float64:
		return x != 0
	case float32:
		return x != 0
	case uint64:
		return x != 0
	case int64:
		return x != 0
	case int:
		return x != 0
	case uint:
		return x != 0
	case byte:
		return x != 0
	case rune:
		return x != 0
	case bool:
		return x
	case string:
		return len(x) != 0
	case List:
		return len(x) != 0
	case Dict:
		return len(x) != 0
	}
	panic(Sprintf("Case not supported in Truth(): %#v", a))
}

func Argv1(argv List) Any {
	if len(argv) != 1+1 {
		panic(Sprintf("Expected 1 arguments, but got %#v", argv))
	}
	return argv[1]
}

func Argv2(argv List) (Any, Any) {
	if len(argv) != 2+1 {
		panic(Sprintf("Expected 2 arguments, but got %#v", argv))
	}
	return argv[1], argv[2]
}

func Argv3(argv List) (Any, Any, Any) {
	if len(argv) != 3+1 {
		panic(Sprintf("Expected 3 arguments, but got %#v", argv))
	}
	return argv[1], argv[2], argv[3]
}

func TArgv1(argv []T) T {
	if len(argv) != 1+1 {
		panic(Sprintf("Expected 1 arguments, but got argv=%#v", argv))
	}
	return argv[1]
}

func TArgv2(argv []T) (T, T) {
	if len(argv) != 2+1 {
		panic(Sprintf("Expected 2 arguments, but got argv=%#v", argv))
	}
	return argv[1], argv[2]
}

func TArgv3(argv []T) (T, T, T) {
	if len(argv) != 3+1 {
		panic(Sprintf("Expected 3 arguments, but got argv=%#v", argv))
	}
	return argv[1], argv[2], argv[3]
}

func cmdMust(fr *Frame, argv List) Any {
	x := Str(argv[1])
	y := Str(argv[2])

	if x == y {
		return argv[2]
	}

	panic("FAILED: must: " + Repr(argv) + " -- x: " + x + " -- y: " + y)
}
func tcmdMust(fr *Frame, argv []T) T {
	xx, yy := TArgv2(argv)
	x := xx.String()
	y := yy.String()

	if x == y {
		return argv[2]
	}

	panic("FAILED: must: " + Repr(argv) + " -- x: " + x + " -- y: " + y)
}

func tcmdIf(fr *Frame, argv []T) T {
	if len(argv) < 3 {
		panic(Sprintf("Too few arguments for if: %#v", argv))
	}
	var cond, yes, no T

	switch len(argv) {
	case 5:
		if argv[3].String() != "else" {
			panic(Sprintf("Expected 'else' at argv[3]: %#v", argv))
		}
		cond, yes, no = argv[1], argv[2], argv[4]
	case 3:
		cond, yes = argv[1], argv[2]
	default:
		panic(Sprintf("Wrong len(argv) for if: %#v", argv))
	}

	if TTruth(fr.TEval(cond)) {
		return fr.TEval(yes)
	}

	if no != nil {
		return fr.TEval(no)
	}

	return Empty
}

func cmdIf(fr *Frame, argv List) Any {
	if len(argv) < 3+1 {
		panic(Sprintf("Too few arguments for if: %#v", argv))
	}
	var cond, yes, no Any

	switch len(argv) {
	case 5:
		if argv[3] != "else" {
			panic(Sprintf("Expected 'else' at argv[3]: %#v", argv))
		}
		cond, yes, no = argv[1], argv[2], argv[4]
	case 3:
		cond, yes = argv[1], argv[2]
	default:
		panic(Sprintf("Wrong len(argv) for if: %#v", argv))
	}

	if Truth(fr.Eval(cond)) {
		return fr.Eval(yes)
	}

	if no != nil {
		return fr.Eval(no)
	}

	return nil
}

func tcmdGet(fr *Frame, argv []T) T {
	name := TArgv1(argv)
	return fr.TGetVar(name.String())
}

func tcmdSet(fr *Frame, argv []T) T {
	name, x := TArgv2(argv)
	fr.TSetVar(name.String(), x)
	return x
}

func tcmdPuts(fr *Frame, argv []T) T {
	// TODO:  accept a Writer as first arg.
	out := TArgv1(argv)
	Println(out)
	return Empty
}

func cmdProc(fr *Frame, argv List) Any {
	// This is an old cmdProc that installs a new TCommand functions.
	// It's an ugly mix of old & new.   FIXME
	name, aa, body := Argv3(argv)
	alist := ParseList(aa)
	astrs := make([]string, len(alist))
	for i, arg := range alist {
		astr := Str(arg)
		if !IsLocal(astr) {
			panic(Sprintf("Cannot use nonlocal name %q for argument in proc", arg))
		}
		astrs[i] = astr
	}
	n := len(alist) + 1

	tcmd := func(fr2 *Frame, argv2 []T) T {
		if argv2 == nil {
			// Debug Data, if invoked with nil argv2.
			return MkTl(newlist(argv))
		}
		if len(argv2) != n {
			panic(Sprintf("Proc %q expects args %#v but got %#v", name, aa, argv2))
		}
		fr3 := fr2.NewFrame()
		for i, arg := range astrs {
			fr3.TSetVar(arg, argv2[i+1])
		}
		return fr3.TEval(new(body))
	}

	fr.G.TCmds[Str(name)] = tcmd
	return nil
}

func cmdLs(fr *Frame, argv List) Any {
	name := Argv1(argv)
	fn := fr.G.Cmds[Str(name)]
	return fn(fr, nil)
}

func cmdSLen(fr *Frame, argv List) Any {
	a := Argv1(argv)
	return float64(len(Str(a)))
}

func cmdLLen(fr *Frame, argv List) Any {
	a := Argv1(argv)
	return float64(len(ParseList(a)))
}

func cmdList(fr *Frame, argv List) Any {
	return argv[1:]
}

func tcmdLAt(fr *Frame, argv []T) T {
	v, j := TArgv2(argv)
	i := j.Int()
	return new(ParseList(v)[i])
}

func tcmdSAt(fr *Frame, argv []T) T {
	s, j := TArgv2(argv)
	i := j.Int()
	return MkTs(s.String()[i : i+1])
}

func cmdNil(fr *Frame, argv List) Any { // TODO
	return http.Handler(nil)
}

func cmdHttpHandler(fr *Frame, argv List) Any {
	return func (w http.ResponseWriter, r *http.Request) {
		v := make(List, len(argv)-1)
		copy(v, argv[1:])
		v = LAppend(v, w)
		v = LAppend(v, r)
		_ = fr.Apply(v)
	}
}

<commit_msg>Delete Truth<commit_after>package terp

import (
	. "fmt"
	"log"
	"net/http"
	"strconv"
)

var _ = log.Printf
var _ = strconv.ParseFloat

var TBuiltins map[string]TCommand = make(map[string]TCommand, 0)

func (fr *Frame) initTBuiltins() {
	TBuiltins["+"] = MkChainingBinaryFlopTCmd(fr, 0.0, func(a, b float64) float64 { return a + b })
	TBuiltins["*"] = MkChainingBinaryFlopTCmd(fr, 1.0, func(a, b float64) float64 { return a * b })
	TBuiltins["-"] = MkBinaryFlopTCmd(fr, func(a, b float64) float64 { return a - b })
	TBuiltins["/"] = MkBinaryFlopTCmd(fr, func(a, b float64) float64 { return a / b })

	TBuiltins["=="] = MkBinaryFlopBoolTCmd(fr, func(a, b float64) bool { return (a == b) })
	TBuiltins["!="] = MkBinaryFlopBoolTCmd(fr, func(a, b float64) bool { return (a != b) })
	TBuiltins["<"] = MkBinaryFlopBoolTCmd(fr, func(a, b float64) bool { return (a < b) })
	TBuiltins["<="] = MkBinaryFlopBoolTCmd(fr, func(a, b float64) bool { return (a <= b) })
	TBuiltins[">"] = MkBinaryFlopBoolTCmd(fr, func(a, b float64) bool { return (a > b) })
	TBuiltins[">="] = MkBinaryFlopBoolTCmd(fr, func(a, b float64) bool { return (a >= b) })
	TBuiltins["must"] = tcmdMust

	TBuiltins["if"] = tcmdIf
	TBuiltins["get"] = tcmdGet
	TBuiltins["set"] = tcmdSet
	TBuiltins["puts"] = tcmdPuts
	TBuiltins["proc"] = newcmd(cmdProc)
	TBuiltins["ls"] = newcmd(cmdLs)
	TBuiltins["slen"] = newcmd(cmdSLen)
	TBuiltins["llen"] = newcmd(cmdLLen)
	TBuiltins["list"] = newcmd(cmdList)
	TBuiltins["sat"] = tcmdSAt  // a.k.a. string index
	TBuiltins["lat"] = tcmdLAt  // a.k.a. lindex
	TBuiltins["nil"] = newcmd(cmdNil)
	TBuiltins["http_handler"] = newcmd(cmdHttpHandler)
}
type BinaryFlop func(a, b float64) float64
type BinaryFlopBool func(a, b float64) bool

func MkBinaryFlopTCmd(fr *Frame, flop BinaryFlop) TCommand {
	return func(fr *Frame, argv []T) T {
		a, b := TArgv2(argv)
		return MkTf(flop(a.Float(), b.Float()))
	}
}

func MkBinaryFlopBoolTCmd(fr *Frame, flop BinaryFlopBool) TCommand {
	return func(fr *Frame, argv []T) T {
		a, b := TArgv2(argv)
		return MkTb(flop(a.Float(), b.Float()))
	}
}

func MkChainingBinaryFlopTCmd(fr *Frame, starter float64, flop BinaryFlop) TCommand {
	return func(fr *Frame, argv []T) T {
		z := starter // Be sure not to modify starter!  It is captured.
		for _, a := range argv[1:] {
			z = flop(z, a.Float())
		}
		return MkTf(z)
	}
}

func TTruth(a T) bool {
	switch x := a.(type) {
	case Tf:
		return x.f != 0
	case Ts:
		return len(x.s) > 0
	case Tl:
		return len(x.l) > 0
	}
	// To Do: Value(nil) Value(false) are false.
	return true
}
func Argv1(argv List) Any {
	if len(argv) != 1+1 {
		panic(Sprintf("Expected 1 arguments, but got %#v", argv))
	}
	return argv[1]
}

func Argv2(argv List) (Any, Any) {
	if len(argv) != 2+1 {
		panic(Sprintf("Expected 2 arguments, but got %#v", argv))
	}
	return argv[1], argv[2]
}

func Argv3(argv List) (Any, Any, Any) {
	if len(argv) != 3+1 {
		panic(Sprintf("Expected 3 arguments, but got %#v", argv))
	}
	return argv[1], argv[2], argv[3]
}

func TArgv1(argv []T) T {
	if len(argv) != 1+1 {
		panic(Sprintf("Expected 1 arguments, but got argv=%#v", argv))
	}
	return argv[1]
}

func TArgv2(argv []T) (T, T) {
	if len(argv) != 2+1 {
		panic(Sprintf("Expected 2 arguments, but got argv=%#v", argv))
	}
	return argv[1], argv[2]
}

func TArgv3(argv []T) (T, T, T) {
	if len(argv) != 3+1 {
		panic(Sprintf("Expected 3 arguments, but got argv=%#v", argv))
	}
	return argv[1], argv[2], argv[3]
}

func cmdMust(fr *Frame, argv List) Any {
	x := Str(argv[1])
	y := Str(argv[2])

	if x == y {
		return argv[2]
	}

	panic("FAILED: must: " + Repr(argv) + " -- x: " + x + " -- y: " + y)
}
func tcmdMust(fr *Frame, argv []T) T {
	xx, yy := TArgv2(argv)
	x := xx.String()
	y := yy.String()

	if x == y {
		return argv[2]
	}

	panic("FAILED: must: " + Repr(argv) + " -- x: " + x + " -- y: " + y)
}

func tcmdIf(fr *Frame, argv []T) T {
	if len(argv) < 3 {
		panic(Sprintf("Too few arguments for if: %#v", argv))
	}
	var cond, yes, no T

	switch len(argv) {
	case 5:
		if argv[3].String() != "else" {
			panic(Sprintf("Expected 'else' at argv[3]: %#v", argv))
		}
		cond, yes, no = argv[1], argv[2], argv[4]
	case 3:
		cond, yes = argv[1], argv[2]
	default:
		panic(Sprintf("Wrong len(argv) for if: %#v", argv))
	}

	if TTruth(fr.TEval(cond)) {
		return fr.TEval(yes)
	}

	if no != nil {
		return fr.TEval(no)
	}

	return Empty
}

func tcmdGet(fr *Frame, argv []T) T {
	name := TArgv1(argv)
	return fr.TGetVar(name.String())
}

func tcmdSet(fr *Frame, argv []T) T {
	name, x := TArgv2(argv)
	fr.TSetVar(name.String(), x)
	return x
}

func tcmdPuts(fr *Frame, argv []T) T {
	// TODO:  accept a Writer as first arg.
	out := TArgv1(argv)
	Println(out)
	return Empty
}

func cmdProc(fr *Frame, argv List) Any {
	// This is an old cmdProc that installs a new TCommand functions.
	// It's an ugly mix of old & new.   FIXME
	name, aa, body := Argv3(argv)
	alist := ParseList(aa)
	astrs := make([]string, len(alist))
	for i, arg := range alist {
		astr := Str(arg)
		if !IsLocal(astr) {
			panic(Sprintf("Cannot use nonlocal name %q for argument in proc", arg))
		}
		astrs[i] = astr
	}
	n := len(alist) + 1

	tcmd := func(fr2 *Frame, argv2 []T) T {
		if argv2 == nil {
			// Debug Data, if invoked with nil argv2.
			return MkTl(newlist(argv))
		}
		if len(argv2) != n {
			panic(Sprintf("Proc %q expects args %#v but got %#v", name, aa, argv2))
		}
		fr3 := fr2.NewFrame()
		for i, arg := range astrs {
			fr3.TSetVar(arg, argv2[i+1])
		}
		return fr3.TEval(new(body))
	}

	fr.G.TCmds[Str(name)] = tcmd
	return nil
}

func cmdLs(fr *Frame, argv List) Any {
	name := Argv1(argv)
	fn := fr.G.Cmds[Str(name)]
	return fn(fr, nil)
}

func cmdSLen(fr *Frame, argv List) Any {
	a := Argv1(argv)
	return float64(len(Str(a)))
}

func cmdLLen(fr *Frame, argv List) Any {
	a := Argv1(argv)
	return float64(len(ParseList(a)))
}

func cmdList(fr *Frame, argv List) Any {
	return argv[1:]
}

func tcmdLAt(fr *Frame, argv []T) T {
	v, j := TArgv2(argv)
	i := j.Int()
	return new(ParseList(v)[i])
}

func tcmdSAt(fr *Frame, argv []T) T {
	s, j := TArgv2(argv)
	i := j.Int()
	return MkTs(s.String()[i : i+1])
}

func cmdNil(fr *Frame, argv List) Any { // TODO
	return http.Handler(nil)
}

func cmdHttpHandler(fr *Frame, argv List) Any {
	return func (w http.ResponseWriter, r *http.Request) {
		v := make(List, len(argv)-1)
		copy(v, argv[1:])
		v = LAppend(v, w)
		v = LAppend(v, r)
		_ = fr.Apply(v)
	}
}

<|endoftext|>