<commit_before>// +build linux

package systemd

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"time"

	systemd "github.com/coreos/go-systemd/dbus"
	"github.com/docker/libcontainer/cgroups"
	"github.com/docker/libcontainer/cgroups/fs"
	"github.com/docker/libcontainer/configs"
	"github.com/godbus/dbus"
)

type Manager struct {
	Cgroups *configs.Cgroup
	Paths   map[string]string
}

type subsystem interface {
	// Returns the stats, as 'stats', corresponding to the cgroup under 'path'.
	GetStats(path string, stats *cgroups.Stats) error
	// Set the cgroup represented by cgroup.
	Set(path string, cgroup *configs.Cgroup) error
}

var subsystems = map[string]subsystem{
	"devices":    &fs.DevicesGroup{},
	"memory":     &fs.MemoryGroup{},
	"cpu":        &fs.CpuGroup{},
	"cpuset":     &fs.CpusetGroup{},
	"cpuacct":    &fs.CpuacctGroup{},
	"blkio":      &fs.BlkioGroup{},
	"perf_event": &fs.PerfEventGroup{},
	"freezer":    &fs.FreezerGroup{},
}

const (
	testScopeWait = 4
)

var (
	connLock                        sync.Mutex
	theConn                         *systemd.Conn
	hasStartTransientUnit           bool
	hasTransientDefaultDependencies bool
)

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

func UseSystemd() bool {
	s, err := os.Stat("/run/systemd/system")
	if err != nil || !s.IsDir() {
		return false
	}

	connLock.Lock()
	defer connLock.Unlock()

	if theConn == nil {
		var err error
		theConn, err = systemd.New()
		if err != nil {
			return false
		}

		// Assume we have StartTransientUnit
		hasStartTransientUnit = true

		// But if we get UnknownMethod error we don't
		if _, err := theConn.StartTransientUnit("test.scope", "invalid"); err != nil {
			if dbusError, ok := err.(dbus.Error); ok {
				if dbusError.Name == "org.freedesktop.DBus.Error.UnknownMethod" {
					hasStartTransientUnit = false
					return hasStartTransientUnit
				}
			}
		}

		// Ensure the scope name we use doesn't exist. Use the Pid to
		// avoid collisions between multiple libcontainer users on a
		// single host.
		scope := fmt.Sprintf("libcontainer-%d-systemd-test-default-dependencies.scope", os.Getpid())
		testScopeExists := true
		for i := 0; i <= testScopeWait; i++ {
			if _, err := theConn.StopUnit(scope, "replace"); err != nil {
				if dbusError, ok := err.(dbus.Error); ok {
					if strings.Contains(dbusError.Name, "org.freedesktop.systemd1.NoSuchUnit") {
						testScopeExists = false
						break
					}
				}
			}
			time.Sleep(time.Millisecond)
		}

		// Bail out if we can't kill this scope without testing for DefaultDependencies
		if testScopeExists {
			return hasStartTransientUnit
		}

		// Assume StartTransientUnit on a scope allows DefaultDependencies
		hasTransientDefaultDependencies = true
		ddf := newProp("DefaultDependencies", false)
		if _, err := theConn.StartTransientUnit(scope, "replace", ddf); err != nil {
			if dbusError, ok := err.(dbus.Error); ok {
				if strings.Contains(dbusError.Name, "org.freedesktop.DBus.Error.PropertyReadOnly") {
					hasTransientDefaultDependencies = false
				}
			}
		}

		// Not critical because of the stop unit logic above.
		theConn.StopUnit(scope, "replace")
	}
	return hasStartTransientUnit
}

func getIfaceForUnit(unitName string) string {
	if strings.HasSuffix(unitName, ".scope") {
		return "Scope"
	}
	if strings.HasSuffix(unitName, ".service") {
		return "Service"
	}
	return "Unit"
}

func (m *Manager) Apply(pid int) error {
	var (
		c          = m.Cgroups
		unitName   = getUnitName(c)
		slice      = "system.slice"
		properties []systemd.Property
	)

	if c.Slice != "" {
		slice = c.Slice
	}

	properties = append(properties,
		systemd.PropSlice(slice),
		systemd.PropDescription("docker container "+c.Name),
		newProp("PIDs", []uint32{uint32(pid)}),
	)

	// Always enable accounting, this gets us the same behaviour as the fs implementation,
	// plus the kernel has some problems with joining the memory cgroup at a later time.
	properties = append(properties,
		newProp("MemoryAccounting", true),
		newProp("CPUAccounting", true),
		newProp("BlockIOAccounting", true))

	if hasTransientDefaultDependencies {
		properties = append(properties,
			newProp("DefaultDependencies", false))
	}

	if c.Memory != 0 {
		properties = append(properties,
			newProp("MemoryLimit", uint64(c.Memory)))
	}
	// TODO: MemoryReservation and MemorySwap not available in systemd

	if c.CpuShares != 0 {
		properties = append(properties,
			newProp("CPUShares", uint64(c.CpuShares)))
	}

	if c.BlkioWeight != 0 {
		properties = append(properties,
			newProp("BlockIOWeight", uint64(c.BlkioWeight)))
	}

	if _, err := theConn.StartTransientUnit(unitName, "replace", properties...); err != nil {
		return err
	}

	if err := joinDevices(c, pid); err != nil {
		return err
	}

	// TODO: CpuQuota and CpuPeriod not available in systemd
	// we need to manually join the cpu.cfs_quota_us and cpu.cfs_period_us
	if err := joinCpu(c, pid); err != nil {
		return err
	}

	// -1 disables memorySwap
	if c.MemorySwap >= 0 && c.Memory != 0 {
		if err := joinMemory(c, pid); err != nil {
			return err
		}

	}

	// we need to manually join the freezer and cpuset cgroup in systemd
	// because it does not currently support it via the dbus api.
	if err := joinFreezer(c, pid); err != nil {
		return err
	}

	if err := joinCpuset(c, pid); err != nil {
		return err
	}

	paths := make(map[string]string)
	for sysname := range subsystems {
		subsystemPath, err := getSubsystemPath(m.Cgroups, sysname)
		if err != nil {
			// Don't fail if a cgroup hierarchy was not found, just skip this subsystem
			if cgroups.IsNotFound(err) {
				continue
			}
			return err
		}
		paths[sysname] = subsystemPath
	}

	m.Paths = paths

	return nil
}

func (m *Manager) Destroy() error {
	return cgroups.RemovePaths(m.Paths)
}

func (m *Manager) GetPaths() map[string]string {
	return m.Paths
}

func writeFile(dir, file, data string) error {
	return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700)
}

func joinCpu(c *configs.Cgroup, pid int) error {
	path, err := getSubsystemPath(c, "cpu")
	if err != nil {
		return err
	}
	if c.CpuQuota != 0 {
		if err = ioutil.WriteFile(filepath.Join(path, "cpu.cfs_quota_us"), []byte(strconv.FormatInt(c.CpuQuota, 10)), 0700); err != nil {
			return err
		}
	}
	if c.CpuPeriod != 0 {
		if err = ioutil.WriteFile(filepath.Join(path, "cpu.cfs_period_us"), []byte(strconv.FormatInt(c.CpuPeriod, 10)), 0700); err != nil {
			return err
		}
	}
	return nil
}

func joinFreezer(c *configs.Cgroup, pid int) error {
	path, err := getSubsystemPath(c, "freezer")
	if err != nil {
		return err
	}

	if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) {
		return err
	}

	return ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700)
}

func getSubsystemPath(c *configs.Cgroup, subsystem string) (string, error) {
	mountpoint, err := cgroups.FindCgroupMountpoint(subsystem)
	if err != nil {
		return "", err
	}

	initPath, err := cgroups.GetInitCgroupDir(subsystem)
	if err != nil {
		return "", err
	}

	slice := "system.slice"
	if c.Slice != "" {
		slice = c.Slice
	}

	return filepath.Join(mountpoint, initPath, slice, getUnitName(c)), nil
}

func (m *Manager) Freeze(state configs.FreezerState) error {
	path, err := getSubsystemPath(m.Cgroups, "freezer")
	if err != nil {
		return err
	}

	if err := ioutil.WriteFile(filepath.Join(path, "freezer.state"), []byte(state), 0); err != nil {
		return err
	}
	for {
		state_, err := ioutil.ReadFile(filepath.Join(path, "freezer.state"))
		if err != nil {
			return err
		}
		if string(state) == string(bytes.TrimSpace(state_)) {
			break
		}
		time.Sleep(1 * time.Millisecond)
	}

	m.Cgroups.Freezer = state

	return nil
}

func (m *Manager) GetPids() ([]int, error) {
	path, err := getSubsystemPath(m.Cgroups, "cpu")
	if err != nil {
		return nil, err
	}

	return cgroups.ReadProcsFile(path)
}

func (m *Manager) GetStats() (*cgroups.Stats, error) {
	stats := cgroups.NewStats()
	for name, path := range m.Paths {
		sys, ok := subsystems[name]
		if !ok || !cgroups.PathExists(path) {
			continue
		}
		if err := sys.GetStats(path, stats); err != nil {
			return nil, err
		}
	}

	return stats, nil
}

func (m *Manager) Set(container *configs.Config) error {
	panic("not implemented")
}

func getUnitName(c *configs.Cgroup) string {
	return fmt.Sprintf("%s-%s.scope", c.Parent, c.Name)
}

// Atm we can't use the systemd device support because of two missing things:
// * Support for wildcards to allow mknod on any device
// * Support for wildcards to allow /dev/pts support
//
// The second is available in more recent systemd as "char-pts", but not in e.g. v208 which is
// in wide use. When both these are availalable we will be able to switch, but need to keep the old
// implementation for backwards compat.
//
// Note: we can't use systemd to set up the initial limits, and then change the cgroup
// because systemd will re-write the device settings if it needs to re-apply the cgroup context.
// This happens at least for v208 when any sibling unit is started.
func joinDevices(c *configs.Cgroup, pid int) error {
	path, err := getSubsystemPath(c, "devices")
	if err != nil {
		return err
	}

	if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) {
		return err
	}

	if err := ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700); err != nil {
		return err
	}

	if !c.AllowAllDevices {
		if err := writeFile(path, "devices.deny", "a"); err != nil {
			return err
		}
	}
	for _, dev := range c.AllowedDevices {
		if err := writeFile(path, "devices.allow", dev.CgroupString()); err != nil {
			return err
		}
	}
	return nil
}

// Symmetrical public function to update device based cgroups.  Also available
// in the fs implementation.
func ApplyDevices(c *configs.Cgroup, pid int) error {
	return joinDevices(c, pid)
}

func joinMemory(c *configs.Cgroup, pid int) error {
	memorySwap := c.MemorySwap

	if memorySwap == 0 {
		// By default, MemorySwap is set to twice the size of RAM.
		memorySwap = c.Memory * 2
	}

	path, err := getSubsystemPath(c, "memory")
	if err != nil {
		return err
	}

	return ioutil.WriteFile(filepath.Join(path, "memory.memsw.limit_in_bytes"), []byte(strconv.FormatInt(memorySwap, 10)), 0700)
}

// systemd does not atm set up the cpuset controller, so we must manually
// join it. Additionally that is a very finicky controller where each
// level must have a full setup as the default for a new directory is "no cpus"
func joinCpuset(c *configs.Cgroup, pid int) error {
	path, err := getSubsystemPath(c, "cpuset")
	if err != nil {
		return err
	}

	s := &fs.CpusetGroup{}

	return s.ApplyDir(path, c, pid)
}
<commit_msg>don't change state if systemd freeze failed<commit_after>// +build linux

package systemd

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"time"

	systemd "github.com/coreos/go-systemd/dbus"
	"github.com/docker/libcontainer/cgroups"
	"github.com/docker/libcontainer/cgroups/fs"
	"github.com/docker/libcontainer/configs"
	"github.com/godbus/dbus"
)

type Manager struct {
	Cgroups *configs.Cgroup
	Paths   map[string]string
}

type subsystem interface {
	// Returns the stats, as 'stats', corresponding to the cgroup under 'path'.
	GetStats(path string, stats *cgroups.Stats) error
	// Set the cgroup represented by cgroup.
	Set(path string, cgroup *configs.Cgroup) error
}

var subsystems = map[string]subsystem{
	"devices":    &fs.DevicesGroup{},
	"memory":     &fs.MemoryGroup{},
	"cpu":        &fs.CpuGroup{},
	"cpuset":     &fs.CpusetGroup{},
	"cpuacct":    &fs.CpuacctGroup{},
	"blkio":      &fs.BlkioGroup{},
	"perf_event": &fs.PerfEventGroup{},
	"freezer":    &fs.FreezerGroup{},
}

const (
	testScopeWait = 4
)

var (
	connLock                        sync.Mutex
	theConn                         *systemd.Conn
	hasStartTransientUnit           bool
	hasTransientDefaultDependencies bool
)

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

func UseSystemd() bool {
	s, err := os.Stat("/run/systemd/system")
	if err != nil || !s.IsDir() {
		return false
	}

	connLock.Lock()
	defer connLock.Unlock()

	if theConn == nil {
		var err error
		theConn, err = systemd.New()
		if err != nil {
			return false
		}

		// Assume we have StartTransientUnit
		hasStartTransientUnit = true

		// But if we get UnknownMethod error we don't
		if _, err := theConn.StartTransientUnit("test.scope", "invalid"); err != nil {
			if dbusError, ok := err.(dbus.Error); ok {
				if dbusError.Name == "org.freedesktop.DBus.Error.UnknownMethod" {
					hasStartTransientUnit = false
					return hasStartTransientUnit
				}
			}
		}

		// Ensure the scope name we use doesn't exist. Use the Pid to
		// avoid collisions between multiple libcontainer users on a
		// single host.
		scope := fmt.Sprintf("libcontainer-%d-systemd-test-default-dependencies.scope", os.Getpid())
		testScopeExists := true
		for i := 0; i <= testScopeWait; i++ {
			if _, err := theConn.StopUnit(scope, "replace"); err != nil {
				if dbusError, ok := err.(dbus.Error); ok {
					if strings.Contains(dbusError.Name, "org.freedesktop.systemd1.NoSuchUnit") {
						testScopeExists = false
						break
					}
				}
			}
			time.Sleep(time.Millisecond)
		}

		// Bail out if we can't kill this scope without testing for DefaultDependencies
		if testScopeExists {
			return hasStartTransientUnit
		}

		// Assume StartTransientUnit on a scope allows DefaultDependencies
		hasTransientDefaultDependencies = true
		ddf := newProp("DefaultDependencies", false)
		if _, err := theConn.StartTransientUnit(scope, "replace", ddf); err != nil {
			if dbusError, ok := err.(dbus.Error); ok {
				if strings.Contains(dbusError.Name, "org.freedesktop.DBus.Error.PropertyReadOnly") {
					hasTransientDefaultDependencies = false
				}
			}
		}

		// Not critical because of the stop unit logic above.
		theConn.StopUnit(scope, "replace")
	}
	return hasStartTransientUnit
}

func getIfaceForUnit(unitName string) string {
	if strings.HasSuffix(unitName, ".scope") {
		return "Scope"
	}
	if strings.HasSuffix(unitName, ".service") {
		return "Service"
	}
	return "Unit"
}

func (m *Manager) Apply(pid int) error {
	var (
		c          = m.Cgroups
		unitName   = getUnitName(c)
		slice      = "system.slice"
		properties []systemd.Property
	)

	if c.Slice != "" {
		slice = c.Slice
	}

	properties = append(properties,
		systemd.PropSlice(slice),
		systemd.PropDescription("docker container "+c.Name),
		newProp("PIDs", []uint32{uint32(pid)}),
	)

	// Always enable accounting, this gets us the same behaviour as the fs implementation,
	// plus the kernel has some problems with joining the memory cgroup at a later time.
	properties = append(properties,
		newProp("MemoryAccounting", true),
		newProp("CPUAccounting", true),
		newProp("BlockIOAccounting", true))

	if hasTransientDefaultDependencies {
		properties = append(properties,
			newProp("DefaultDependencies", false))
	}

	if c.Memory != 0 {
		properties = append(properties,
			newProp("MemoryLimit", uint64(c.Memory)))
	}
	// TODO: MemoryReservation and MemorySwap not available in systemd

	if c.CpuShares != 0 {
		properties = append(properties,
			newProp("CPUShares", uint64(c.CpuShares)))
	}

	if c.BlkioWeight != 0 {
		properties = append(properties,
			newProp("BlockIOWeight", uint64(c.BlkioWeight)))
	}

	if _, err := theConn.StartTransientUnit(unitName, "replace", properties...); err != nil {
		return err
	}

	if err := joinDevices(c, pid); err != nil {
		return err
	}

	// TODO: CpuQuota and CpuPeriod not available in systemd
	// we need to manually join the cpu.cfs_quota_us and cpu.cfs_period_us
	if err := joinCpu(c, pid); err != nil {
		return err
	}

	// -1 disables memorySwap
	if c.MemorySwap >= 0 && c.Memory != 0 {
		if err := joinMemory(c, pid); err != nil {
			return err
		}

	}

	// we need to manually join the freezer and cpuset cgroup in systemd
	// because it does not currently support it via the dbus api.
	if err := joinFreezer(c, pid); err != nil {
		return err
	}

	if err := joinCpuset(c, pid); err != nil {
		return err
	}

	paths := make(map[string]string)
	for sysname := range subsystems {
		subsystemPath, err := getSubsystemPath(m.Cgroups, sysname)
		if err != nil {
			// Don't fail if a cgroup hierarchy was not found, just skip this subsystem
			if cgroups.IsNotFound(err) {
				continue
			}
			return err
		}
		paths[sysname] = subsystemPath
	}

	m.Paths = paths

	return nil
}

func (m *Manager) Destroy() error {
	return cgroups.RemovePaths(m.Paths)
}

func (m *Manager) GetPaths() map[string]string {
	return m.Paths
}

func writeFile(dir, file, data string) error {
	return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700)
}

func joinCpu(c *configs.Cgroup, pid int) error {
	path, err := getSubsystemPath(c, "cpu")
	if err != nil {
		return err
	}
	if c.CpuQuota != 0 {
		if err = ioutil.WriteFile(filepath.Join(path, "cpu.cfs_quota_us"), []byte(strconv.FormatInt(c.CpuQuota, 10)), 0700); err != nil {
			return err
		}
	}
	if c.CpuPeriod != 0 {
		if err = ioutil.WriteFile(filepath.Join(path, "cpu.cfs_period_us"), []byte(strconv.FormatInt(c.CpuPeriod, 10)), 0700); err != nil {
			return err
		}
	}
	return nil
}

func joinFreezer(c *configs.Cgroup, pid int) error {
	path, err := getSubsystemPath(c, "freezer")
	if err != nil {
		return err
	}

	if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) {
		return err
	}

	return ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700)
}

func getSubsystemPath(c *configs.Cgroup, subsystem string) (string, error) {
	mountpoint, err := cgroups.FindCgroupMountpoint(subsystem)
	if err != nil {
		return "", err
	}

	initPath, err := cgroups.GetInitCgroupDir(subsystem)
	if err != nil {
		return "", err
	}

	slice := "system.slice"
	if c.Slice != "" {
		slice = c.Slice
	}

	return filepath.Join(mountpoint, initPath, slice, getUnitName(c)), nil
}

func (m *Manager) Freeze(state configs.FreezerState) error {
	path, err := getSubsystemPath(m.Cgroups, "freezer")
	if err != nil {
		return err
	}

	prevState := m.Cgroups.Freezer
	m.Cgroups.Freezer = state

	freezer := subsystems["freezer"]
	err = freezer.Set(path, m.Cgroups)
	if err != nil {
		m.Cgroups.Freezer = prevState
		return err
	}

	return nil
}

func (m *Manager) GetPids() ([]int, error) {
	path, err := getSubsystemPath(m.Cgroups, "cpu")
	if err != nil {
		return nil, err
	}

	return cgroups.ReadProcsFile(path)
}

func (m *Manager) GetStats() (*cgroups.Stats, error) {
	stats := cgroups.NewStats()
	for name, path := range m.Paths {
		sys, ok := subsystems[name]
		if !ok || !cgroups.PathExists(path) {
			continue
		}
		if err := sys.GetStats(path, stats); err != nil {
			return nil, err
		}
	}

	return stats, nil
}

func (m *Manager) Set(container *configs.Config) error {
	panic("not implemented")
}

func getUnitName(c *configs.Cgroup) string {
	return fmt.Sprintf("%s-%s.scope", c.Parent, c.Name)
}

// Atm we can't use the systemd device support because of two missing things:
// * Support for wildcards to allow mknod on any device
// * Support for wildcards to allow /dev/pts support
//
// The second is available in more recent systemd as "char-pts", but not in e.g. v208 which is
// in wide use. When both these are availalable we will be able to switch, but need to keep the old
// implementation for backwards compat.
//
// Note: we can't use systemd to set up the initial limits, and then change the cgroup
// because systemd will re-write the device settings if it needs to re-apply the cgroup context.
// This happens at least for v208 when any sibling unit is started.
func joinDevices(c *configs.Cgroup, pid int) error {
	path, err := getSubsystemPath(c, "devices")
	if err != nil {
		return err
	}

	if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) {
		return err
	}

	if err := ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700); err != nil {
		return err
	}

	if !c.AllowAllDevices {
		if err := writeFile(path, "devices.deny", "a"); err != nil {
			return err
		}
	}
	for _, dev := range c.AllowedDevices {
		if err := writeFile(path, "devices.allow", dev.CgroupString()); err != nil {
			return err
		}
	}
	return nil
}

// Symmetrical public function to update device based cgroups.  Also available
// in the fs implementation.
func ApplyDevices(c *configs.Cgroup, pid int) error {
	return joinDevices(c, pid)
}

func joinMemory(c *configs.Cgroup, pid int) error {
	memorySwap := c.MemorySwap

	if memorySwap == 0 {
		// By default, MemorySwap is set to twice the size of RAM.
		memorySwap = c.Memory * 2
	}

	path, err := getSubsystemPath(c, "memory")
	if err != nil {
		return err
	}

	return ioutil.WriteFile(filepath.Join(path, "memory.memsw.limit_in_bytes"), []byte(strconv.FormatInt(memorySwap, 10)), 0700)
}

// systemd does not atm set up the cpuset controller, so we must manually
// join it. Additionally that is a very finicky controller where each
// level must have a full setup as the default for a new directory is "no cpus"
func joinCpuset(c *configs.Cgroup, pid int) error {
	path, err := getSubsystemPath(c, "cpuset")
	if err != nil {
		return err
	}

	s := &fs.CpusetGroup{}

	return s.ApplyDir(path, c, pid)
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors.

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 options

import (
	"fmt"
	"time"

	"github.com/spf13/pflag"

	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/apiserver/pkg/authorization/authorizer"
	"k8s.io/apiserver/pkg/authorization/authorizerfactory"
	"k8s.io/apiserver/pkg/authorization/path"
	"k8s.io/apiserver/pkg/authorization/union"
	"k8s.io/apiserver/pkg/server"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/transport"
	"k8s.io/klog/v2"
)

// DelegatingAuthorizationOptions provides an easy way for composing API servers to delegate their authorization to
// the root kube API server.
// WARNING: never assume that every authenticated incoming request already does authorization.
//          The aggregator in the kube API server does this today, but this behaviour is not
//          guaranteed in the future.
type DelegatingAuthorizationOptions struct {
	// RemoteKubeConfigFile is the file to use to connect to a "normal" kube API server which hosts the
	// SubjectAccessReview.authorization.k8s.io endpoint for checking tokens.
	RemoteKubeConfigFile string
	// RemoteKubeConfigFileOptional is specifying whether not specifying the kubeconfig or
	// a missing in-cluster config will be fatal.
	RemoteKubeConfigFileOptional bool

	// AllowCacheTTL is the length of time that a successful authorization response will be cached
	AllowCacheTTL time.Duration

	// DenyCacheTTL is the length of time that an unsuccessful authorization response will be cached.
	// You generally want more responsive, "deny, try again" flows.
	DenyCacheTTL time.Duration

	// AlwaysAllowPaths are HTTP paths which are excluded from authorization. They can be plain
	// paths or end in * in which case prefix-match is applied. A leading / is optional.
	AlwaysAllowPaths []string

	// AlwaysAllowGroups are groups which are allowed to take any actions.  In kube, this is system:masters.
	AlwaysAllowGroups []string

	// ClientTimeout specifies a time limit for requests made by SubjectAccessReviews client.
	// The default value is set to 10 seconds.
	ClientTimeout time.Duration

	// WebhookRetryBackoff specifies the backoff parameters for the authorization webhook retry logic.
	// This allows us to configure the sleep time at each iteration and the maximum number of retries allowed
	// before we fail the webhook call in order to limit the fan out that ensues when the system is degraded.
	WebhookRetryBackoff *wait.Backoff

	// CustomRoundTripperFn allows for specifying a middleware function for custom HTTP behaviour for the authorization webhook client.
	CustomRoundTripperFn transport.WrapperFunc
}

func NewDelegatingAuthorizationOptions() *DelegatingAuthorizationOptions {
	return &DelegatingAuthorizationOptions{
		// very low for responsiveness, but high enough to handle storms
		AllowCacheTTL:       10 * time.Second,
		DenyCacheTTL:        10 * time.Second,
		ClientTimeout:       10 * time.Second,
		WebhookRetryBackoff: DefaultAuthWebhookRetryBackoff(),
		// This allows the kubelet to always get health and readiness without causing an authorization check.
		// This field can be cleared by callers if they don't want this behavior.
		AlwaysAllowPaths: []string{"/healthz", "/readyz", "/livez"},
		// In an authorization call delegated to a kube-apiserver (the expected common-case), system:masters has full
		// authority in a hard-coded authorizer.  This means that our default can reasonably be to skip an authorization
		// check for system:masters.
		// This field can be cleared by callers if they don't want this behavior.
		AlwaysAllowGroups: []string{"system:masters"},
	}
}

// WithAlwaysAllowGroups appends the list of paths to AlwaysAllowGroups
func (s *DelegatingAuthorizationOptions) WithAlwaysAllowGroups(groups ...string) *DelegatingAuthorizationOptions {
	s.AlwaysAllowGroups = append(s.AlwaysAllowGroups, groups...)
	return s
}

// WithAlwaysAllowPaths appends the list of paths to AlwaysAllowPaths
func (s *DelegatingAuthorizationOptions) WithAlwaysAllowPaths(paths ...string) *DelegatingAuthorizationOptions {
	s.AlwaysAllowPaths = append(s.AlwaysAllowPaths, paths...)
	return s
}

// WithClientTimeout sets the given timeout for SAR client used by this authorizer
func (s *DelegatingAuthorizationOptions) WithClientTimeout(timeout time.Duration) {
	s.ClientTimeout = timeout
}

// WithCustomRetryBackoff sets the custom backoff parameters for the authorization webhook retry logic.
func (s *DelegatingAuthorizationOptions) WithCustomRetryBackoff(backoff wait.Backoff) {
	s.WebhookRetryBackoff = &backoff
}

// WithCustomRoundTripper allows for specifying a middleware function for custom HTTP behaviour for the authorization webhook client.
func (s *DelegatingAuthorizationOptions) WithCustomRoundTripper(rt transport.WrapperFunc) {
	s.CustomRoundTripperFn = rt
}

func (s *DelegatingAuthorizationOptions) Validate() []error {
	if s == nil {
		return nil
	}

	allErrors := []error{}
	if s.WebhookRetryBackoff != nil && s.WebhookRetryBackoff.Steps <= 0 {
		allErrors = append(allErrors, fmt.Errorf("number of webhook retry attempts must be greater than 1, but is: %d", s.WebhookRetryBackoff.Steps))
	}

	return allErrors
}

func (s *DelegatingAuthorizationOptions) AddFlags(fs *pflag.FlagSet) {
	if s == nil {
		return
	}

	var optionalKubeConfigSentence string
	if s.RemoteKubeConfigFileOptional {
		optionalKubeConfigSentence = " This is optional. If empty, all requests not skipped by authorization are forbidden."
	}
	fs.StringVar(&s.RemoteKubeConfigFile, "authorization-kubeconfig", s.RemoteKubeConfigFile,
		"kubeconfig file pointing at the 'core' kubernetes server with enough rights to create "+
			"subjectaccessreviews.authorization.k8s.io."+optionalKubeConfigSentence)

	fs.DurationVar(&s.AllowCacheTTL, "authorization-webhook-cache-authorized-ttl",
		s.AllowCacheTTL,
		"The duration to cache 'authorized' responses from the webhook authorizer.")

	fs.DurationVar(&s.DenyCacheTTL,
		"authorization-webhook-cache-unauthorized-ttl", s.DenyCacheTTL,
		"The duration to cache 'unauthorized' responses from the webhook authorizer.")

	fs.StringSliceVar(&s.AlwaysAllowPaths, "authorization-always-allow-paths", s.AlwaysAllowPaths,
		"A list of HTTP paths to skip during authorization, i.e. these are authorized without "+
			"contacting the 'core' kubernetes server.")
}

func (s *DelegatingAuthorizationOptions) ApplyTo(c *server.AuthorizationInfo) error {
	if s == nil {
		c.Authorizer = authorizerfactory.NewAlwaysAllowAuthorizer()
		return nil
	}

	client, err := s.getClient()
	if err != nil {
		return err
	}

	c.Authorizer, err = s.toAuthorizer(client)
	return err
}

func (s *DelegatingAuthorizationOptions) toAuthorizer(client kubernetes.Interface) (authorizer.Authorizer, error) {
	var authorizers []authorizer.Authorizer

	if len(s.AlwaysAllowGroups) > 0 {
		authorizers = append(authorizers, authorizerfactory.NewPrivilegedGroups(s.AlwaysAllowGroups...))
	}

	if len(s.AlwaysAllowPaths) > 0 {
		a, err := path.NewAuthorizer(s.AlwaysAllowPaths)
		if err != nil {
			return nil, err
		}
		authorizers = append(authorizers, a)
	}

	if client == nil {
		klog.Warning("No authorization-kubeconfig provided, so SubjectAccessReview of authorization tokens won't work.")
	} else {
		cfg := authorizerfactory.DelegatingAuthorizerConfig{
			SubjectAccessReviewClient: client.AuthorizationV1(),
			AllowCacheTTL:             s.AllowCacheTTL,
			DenyCacheTTL:              s.DenyCacheTTL,
			WebhookRetryBackoff:       s.WebhookRetryBackoff,
		}
		delegatedAuthorizer, err := cfg.New()
		if err != nil {
			return nil, err
		}
		authorizers = append(authorizers, delegatedAuthorizer)
	}

	return union.New(authorizers...), nil
}

func (s *DelegatingAuthorizationOptions) getClient() (kubernetes.Interface, error) {
	var clientConfig *rest.Config
	var err error
	if len(s.RemoteKubeConfigFile) > 0 {
		loadingRules := &clientcmd.ClientConfigLoadingRules{ExplicitPath: s.RemoteKubeConfigFile}
		loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})

		clientConfig, err = loader.ClientConfig()
	} else {
		// without the remote kubeconfig file, try to use the in-cluster config.  Most addon API servers will
		// use this path. If it is optional, ignore errors.
		clientConfig, err = rest.InClusterConfig()
		if err != nil && s.RemoteKubeConfigFileOptional {
			if err != rest.ErrNotInCluster {
				klog.Warningf("failed to read in-cluster kubeconfig for delegated authorization: %v", err)
			}
			return nil, nil
		}
	}
	if err != nil {
		return nil, fmt.Errorf("failed to get delegated authorization kubeconfig: %v", err)
	}

	// set high qps/burst limits since this will effectively limit API server responsiveness
	clientConfig.QPS = 200
	clientConfig.Burst = 400
	clientConfig.Timeout = s.ClientTimeout
	if s.CustomRoundTripperFn != nil {
		clientConfig.Wrap(s.CustomRoundTripperFn)
	}

	return kubernetes.NewForConfig(clientConfig)
}
<commit_msg>UPSTREAM: 74956: apiserver: switch authorization to use protobuf client<commit_after>/*
Copyright 2016 The Kubernetes Authors.

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 options

import (
	"fmt"
	"time"

	"github.com/spf13/pflag"

	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/apiserver/pkg/authorization/authorizer"
	"k8s.io/apiserver/pkg/authorization/authorizerfactory"
	"k8s.io/apiserver/pkg/authorization/path"
	"k8s.io/apiserver/pkg/authorization/union"
	"k8s.io/apiserver/pkg/server"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/transport"
	"k8s.io/klog/v2"
)

// DelegatingAuthorizationOptions provides an easy way for composing API servers to delegate their authorization to
// the root kube API server.
// WARNING: never assume that every authenticated incoming request already does authorization.
//          The aggregator in the kube API server does this today, but this behaviour is not
//          guaranteed in the future.
type DelegatingAuthorizationOptions struct {
	// RemoteKubeConfigFile is the file to use to connect to a "normal" kube API server which hosts the
	// SubjectAccessReview.authorization.k8s.io endpoint for checking tokens.
	RemoteKubeConfigFile string
	// RemoteKubeConfigFileOptional is specifying whether not specifying the kubeconfig or
	// a missing in-cluster config will be fatal.
	RemoteKubeConfigFileOptional bool

	// AllowCacheTTL is the length of time that a successful authorization response will be cached
	AllowCacheTTL time.Duration

	// DenyCacheTTL is the length of time that an unsuccessful authorization response will be cached.
	// You generally want more responsive, "deny, try again" flows.
	DenyCacheTTL time.Duration

	// AlwaysAllowPaths are HTTP paths which are excluded from authorization. They can be plain
	// paths or end in * in which case prefix-match is applied. A leading / is optional.
	AlwaysAllowPaths []string

	// AlwaysAllowGroups are groups which are allowed to take any actions.  In kube, this is system:masters.
	AlwaysAllowGroups []string

	// ClientTimeout specifies a time limit for requests made by SubjectAccessReviews client.
	// The default value is set to 10 seconds.
	ClientTimeout time.Duration

	// WebhookRetryBackoff specifies the backoff parameters for the authorization webhook retry logic.
	// This allows us to configure the sleep time at each iteration and the maximum number of retries allowed
	// before we fail the webhook call in order to limit the fan out that ensues when the system is degraded.
	WebhookRetryBackoff *wait.Backoff

	// CustomRoundTripperFn allows for specifying a middleware function for custom HTTP behaviour for the authorization webhook client.
	CustomRoundTripperFn transport.WrapperFunc
}

func NewDelegatingAuthorizationOptions() *DelegatingAuthorizationOptions {
	return &DelegatingAuthorizationOptions{
		// very low for responsiveness, but high enough to handle storms
		AllowCacheTTL:       10 * time.Second,
		DenyCacheTTL:        10 * time.Second,
		ClientTimeout:       10 * time.Second,
		WebhookRetryBackoff: DefaultAuthWebhookRetryBackoff(),
		// This allows the kubelet to always get health and readiness without causing an authorization check.
		// This field can be cleared by callers if they don't want this behavior.
		AlwaysAllowPaths: []string{"/healthz", "/readyz", "/livez"},
		// In an authorization call delegated to a kube-apiserver (the expected common-case), system:masters has full
		// authority in a hard-coded authorizer.  This means that our default can reasonably be to skip an authorization
		// check for system:masters.
		// This field can be cleared by callers if they don't want this behavior.
		AlwaysAllowGroups: []string{"system:masters"},
	}
}

// WithAlwaysAllowGroups appends the list of paths to AlwaysAllowGroups
func (s *DelegatingAuthorizationOptions) WithAlwaysAllowGroups(groups ...string) *DelegatingAuthorizationOptions {
	s.AlwaysAllowGroups = append(s.AlwaysAllowGroups, groups...)
	return s
}

// WithAlwaysAllowPaths appends the list of paths to AlwaysAllowPaths
func (s *DelegatingAuthorizationOptions) WithAlwaysAllowPaths(paths ...string) *DelegatingAuthorizationOptions {
	s.AlwaysAllowPaths = append(s.AlwaysAllowPaths, paths...)
	return s
}

// WithClientTimeout sets the given timeout for SAR client used by this authorizer
func (s *DelegatingAuthorizationOptions) WithClientTimeout(timeout time.Duration) {
	s.ClientTimeout = timeout
}

// WithCustomRetryBackoff sets the custom backoff parameters for the authorization webhook retry logic.
func (s *DelegatingAuthorizationOptions) WithCustomRetryBackoff(backoff wait.Backoff) {
	s.WebhookRetryBackoff = &backoff
}

// WithCustomRoundTripper allows for specifying a middleware function for custom HTTP behaviour for the authorization webhook client.
func (s *DelegatingAuthorizationOptions) WithCustomRoundTripper(rt transport.WrapperFunc) {
	s.CustomRoundTripperFn = rt
}

func (s *DelegatingAuthorizationOptions) Validate() []error {
	if s == nil {
		return nil
	}

	allErrors := []error{}
	if s.WebhookRetryBackoff != nil && s.WebhookRetryBackoff.Steps <= 0 {
		allErrors = append(allErrors, fmt.Errorf("number of webhook retry attempts must be greater than 1, but is: %d", s.WebhookRetryBackoff.Steps))
	}

	return allErrors
}

func (s *DelegatingAuthorizationOptions) AddFlags(fs *pflag.FlagSet) {
	if s == nil {
		return
	}

	var optionalKubeConfigSentence string
	if s.RemoteKubeConfigFileOptional {
		optionalKubeConfigSentence = " This is optional. If empty, all requests not skipped by authorization are forbidden."
	}
	fs.StringVar(&s.RemoteKubeConfigFile, "authorization-kubeconfig", s.RemoteKubeConfigFile,
		"kubeconfig file pointing at the 'core' kubernetes server with enough rights to create "+
			"subjectaccessreviews.authorization.k8s.io."+optionalKubeConfigSentence)

	fs.DurationVar(&s.AllowCacheTTL, "authorization-webhook-cache-authorized-ttl",
		s.AllowCacheTTL,
		"The duration to cache 'authorized' responses from the webhook authorizer.")

	fs.DurationVar(&s.DenyCacheTTL,
		"authorization-webhook-cache-unauthorized-ttl", s.DenyCacheTTL,
		"The duration to cache 'unauthorized' responses from the webhook authorizer.")

	fs.StringSliceVar(&s.AlwaysAllowPaths, "authorization-always-allow-paths", s.AlwaysAllowPaths,
		"A list of HTTP paths to skip during authorization, i.e. these are authorized without "+
			"contacting the 'core' kubernetes server.")
}

func (s *DelegatingAuthorizationOptions) ApplyTo(c *server.AuthorizationInfo) error {
	if s == nil {
		c.Authorizer = authorizerfactory.NewAlwaysAllowAuthorizer()
		return nil
	}

	client, err := s.getClient()
	if err != nil {
		return err
	}

	c.Authorizer, err = s.toAuthorizer(client)
	return err
}

func (s *DelegatingAuthorizationOptions) toAuthorizer(client kubernetes.Interface) (authorizer.Authorizer, error) {
	var authorizers []authorizer.Authorizer

	if len(s.AlwaysAllowGroups) > 0 {
		authorizers = append(authorizers, authorizerfactory.NewPrivilegedGroups(s.AlwaysAllowGroups...))
	}

	if len(s.AlwaysAllowPaths) > 0 {
		a, err := path.NewAuthorizer(s.AlwaysAllowPaths)
		if err != nil {
			return nil, err
		}
		authorizers = append(authorizers, a)
	}

	if client == nil {
		klog.Warning("No authorization-kubeconfig provided, so SubjectAccessReview of authorization tokens won't work.")
	} else {
		cfg := authorizerfactory.DelegatingAuthorizerConfig{
			SubjectAccessReviewClient: client.AuthorizationV1(),
			AllowCacheTTL:             s.AllowCacheTTL,
			DenyCacheTTL:              s.DenyCacheTTL,
			WebhookRetryBackoff:       s.WebhookRetryBackoff,
		}
		delegatedAuthorizer, err := cfg.New()
		if err != nil {
			return nil, err
		}
		authorizers = append(authorizers, delegatedAuthorizer)
	}

	return union.New(authorizers...), nil
}

func (s *DelegatingAuthorizationOptions) getClient() (kubernetes.Interface, error) {
	var clientConfig *rest.Config
	var err error
	if len(s.RemoteKubeConfigFile) > 0 {
		loadingRules := &clientcmd.ClientConfigLoadingRules{ExplicitPath: s.RemoteKubeConfigFile}
		loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})

		clientConfig, err = loader.ClientConfig()
	} else {
		// without the remote kubeconfig file, try to use the in-cluster config.  Most addon API servers will
		// use this path. If it is optional, ignore errors.
		clientConfig, err = rest.InClusterConfig()
		if err != nil && s.RemoteKubeConfigFileOptional {
			if err != rest.ErrNotInCluster {
				klog.Warningf("failed to read in-cluster kubeconfig for delegated authorization: %v", err)
			}
			return nil, nil
		}
	}
	if err != nil {
		return nil, fmt.Errorf("failed to get delegated authorization kubeconfig: %v", err)
	}

	// set high qps/burst limits since this will effectively limit API server responsiveness
	clientConfig.QPS = 200
	clientConfig.Burst = 400
	clientConfig.Timeout = s.ClientTimeout
	if s.CustomRoundTripperFn != nil {
		clientConfig.Wrap(s.CustomRoundTripperFn)
	}

	// make the client use protobuf
	protoConfig := rest.CopyConfig(clientConfig)
	protoConfig.AcceptContentTypes = "application/vnd.kubernetes.protobuf,application/json"
	protoConfig.ContentType = "application/vnd.kubernetes.protobuf"

	return kubernetes.NewForConfig(protoConfig)
}
<|endoftext|>


<commit_before>/*
 * Copyright 2016 Frank Wessels <fwessels@xs4all.nl>
 *
 * 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 s3git

import (
	"encoding/hex"
	"errors"
	"github.com/s3git/s3git-go/internal/core"
	"github.com/s3git/s3git-go/internal/kv"
	"time"
)

type Commit struct {
	Hash      string
	Message   string
	TimeStamp string
	Parent    string
}

// Perform a commit for the repository
func (repo Repository) Commit(message string) (hash string, empty bool, err error) {
	return repo.commit(message, "master", []string{})
}

// Perform a commit for the named branch of the repository
func (repo Repository) CommitToBranch(message, branch string) (hash string, empty bool, err error) {
	return repo.commit(message, branch, []string{})
}

func (repo Repository) commit(message, branch string, parents []string) (hash string, empty bool, err error) {

	warmParents := []string{}
	coldParents := []string{}

	commits, err := kv.ListTopMostCommits()
	if err != nil {
		return "", false, err
	}

	if len(parents) == 0 {
		for c := range commits {
			warmParents = append(warmParents, hex.EncodeToString(c))
		}
		if len(warmParents) > 1 {
			// TODO: Do extra check whether the trees are the same, in that case we can safely ignore the warning
			return "", false, errors.New("Multiple top most commits founds as parents")
		}
	} else {
		for c := range commits {
			p := hex.EncodeToString(c)
			if contains(parents, p) {
				warmParents = append(warmParents, p)
			} else {
				coldParents = append(coldParents, p)
			}
		}
	}

	return repo.commitWithWarmAndColdParents(message, branch, warmParents, coldParents)
}

func contains(s []string, e string) bool {
	for _, a := range s {
		if a == e {
			return true
		}
	}
	return false
}

func (repo Repository) commitWithWarmAndColdParents(message, branch string, warmParents, coldParents []string) (hash string, empty bool, err error) {

	list, err := kv.ListStage()
	if err != nil {
		return "", false, err
	}

	// Create commit object on disk
	commitHash, empty, err := core.StoreCommitObject(message, branch, warmParents, coldParents, list, []string{})
	if err != nil {
		return "", false, err
	}
	if empty {
		return "", true, nil
	}

	// Remove added blobs from staging area
	err = kv.ClearStage()
	if err != nil {
		return "", false, err
	}

	err = core.StorePrefixObject(commitHash)
	if err != nil {
		return "", false, err
	}

	return commitHash, false, nil
}

// List the commits for a repository
func (repo Repository) ListCommits(branch string) (<-chan Commit, error) {

	// TODO: Implement support for branches
	commits, err := kv.ListTopMostCommits()
	if err != nil {
		return nil, err
	}

	inputs := []Commit{}

	for c := range commits {
		commit := hex.EncodeToString(c)
		start, _, err := getCommit(commit)
		if err != nil {
			return nil, err
		}
		inputs = append(inputs, *start)
	}

	result := make(chan Commit)

	go func() {
		defer close(result)

		for {
			if len(inputs) == 1 {
				result <- inputs[0]
				input, done, err := getCommit(inputs[0].Parent)
				if err != nil {
					return
				} else if done {
					return // no more new parent --> we are done
				}
				inputs[0] = *input
			} else if len(inputs) == 2 {
				t1, _ := time.Parse(time.RFC3339, inputs[0].TimeStamp)
				t2, _ := time.Parse(time.RFC3339, inputs[1].TimeStamp)

				if inputs[0].Hash == inputs[1].Hash {
					// Same commit object so discard second instance
					pos := 0
					inputs = append(inputs[0:pos], inputs[pos+1:len(inputs)]...)
					result <- inputs[pos]

					input, done, err := getCommit(inputs[pos].Parent)
					if err != nil {
						return
					} else if done {
						return // no more new parent --> we are done
					}
					inputs[pos] = *input

				} else if t1.After(t2) {
					result <- inputs[0]

					input, done, err := getCommit(inputs[0].Parent)
					if err != nil {
						return
					} else if done {
						return // no more new parent --> we are done
					}
					inputs[0] = *input
				} else {
					result <- inputs[1]

					input, done, err := getCommit(inputs[1].Parent)
					if err != nil {
						return
					} else if done {
						return // no more new parent --> we are done
					}
					inputs[1] = *input
				}
			}
		}
	}()

	return result, nil
}

func getCommit(commit string) (*Commit, bool, error) {
	if commit == "" {
		return nil, true, nil // we are done
	}
	co, err := core.GetCommitObject(commit)
	if err != nil {
		return nil, false, err
	}
	result := Commit{Hash: commit, Message: co.S3gitMessage, TimeStamp: co.S3gitTimeStamp}
	if len(co.S3gitWarmParents) == 1 {
		result.Parent = co.S3gitWarmParents[0]
	} else if len(co.S3gitWarmParents) > 1 {
		// TODO: Add other parents to inputs
		result.Parent = co.S3gitWarmParents[0]
	}
	return &result, false, err
}
<commit_msg>Remove unnecessary TODO (listed elsewhere)<commit_after>/*
 * Copyright 2016 Frank Wessels <fwessels@xs4all.nl>
 *
 * 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 s3git

import (
	"encoding/hex"
	"errors"
	"github.com/s3git/s3git-go/internal/core"
	"github.com/s3git/s3git-go/internal/kv"
	"time"
)

type Commit struct {
	Hash      string
	Message   string
	TimeStamp string
	Parent    string
}

// Perform a commit for the repository
func (repo Repository) Commit(message string) (hash string, empty bool, err error) {
	return repo.commit(message, "master", []string{})
}

// Perform a commit for the named branch of the repository
func (repo Repository) CommitToBranch(message, branch string) (hash string, empty bool, err error) {
	return repo.commit(message, branch, []string{})
}

func (repo Repository) commit(message, branch string, parents []string) (hash string, empty bool, err error) {

	warmParents := []string{}
	coldParents := []string{}

	commits, err := kv.ListTopMostCommits()
	if err != nil {
		return "", false, err
	}

	if len(parents) == 0 {
		for c := range commits {
			warmParents = append(warmParents, hex.EncodeToString(c))
		}
		if len(warmParents) > 1 {
			// TODO: Do extra check whether the trees are the same, in that case we can safely ignore the warning
			return "", false, errors.New("Multiple top most commits founds as parents")
		}
	} else {
		for c := range commits {
			p := hex.EncodeToString(c)
			if contains(parents, p) {
				warmParents = append(warmParents, p)
			} else {
				coldParents = append(coldParents, p)
			}
		}
	}

	return repo.commitWithWarmAndColdParents(message, branch, warmParents, coldParents)
}

func contains(s []string, e string) bool {
	for _, a := range s {
		if a == e {
			return true
		}
	}
	return false
}

func (repo Repository) commitWithWarmAndColdParents(message, branch string, warmParents, coldParents []string) (hash string, empty bool, err error) {

	list, err := kv.ListStage()
	if err != nil {
		return "", false, err
	}

	// Create commit object on disk
	commitHash, empty, err := core.StoreCommitObject(message, branch, warmParents, coldParents, list, []string{})
	if err != nil {
		return "", false, err
	}
	if empty {
		return "", true, nil
	}

	// Remove added blobs from staging area
	err = kv.ClearStage()
	if err != nil {
		return "", false, err
	}

	err = core.StorePrefixObject(commitHash)
	if err != nil {
		return "", false, err
	}

	return commitHash, false, nil
}

// List the commits for a repository
func (repo Repository) ListCommits(branch string) (<-chan Commit, error) {

	commits, err := kv.ListTopMostCommits()
	if err != nil {
		return nil, err
	}

	inputs := []Commit{}

	for c := range commits {
		commit := hex.EncodeToString(c)
		start, _, err := getCommit(commit)
		if err != nil {
			return nil, err
		}
		inputs = append(inputs, *start)
	}

	result := make(chan Commit)

	go func() {
		defer close(result)

		for {
			if len(inputs) == 1 {
				result <- inputs[0]
				input, done, err := getCommit(inputs[0].Parent)
				if err != nil {
					return
				} else if done {
					return // no more new parent --> we are done
				}
				inputs[0] = *input
			} else if len(inputs) == 2 {
				t1, _ := time.Parse(time.RFC3339, inputs[0].TimeStamp)
				t2, _ := time.Parse(time.RFC3339, inputs[1].TimeStamp)

				if inputs[0].Hash == inputs[1].Hash {
					// Same commit object so discard second instance
					pos := 0
					inputs = append(inputs[0:pos], inputs[pos+1:len(inputs)]...)
					result <- inputs[pos]

					input, done, err := getCommit(inputs[pos].Parent)
					if err != nil {
						return
					} else if done {
						return // no more new parent --> we are done
					}
					inputs[pos] = *input

				} else if t1.After(t2) {
					result <- inputs[0]

					input, done, err := getCommit(inputs[0].Parent)
					if err != nil {
						return
					} else if done {
						return // no more new parent --> we are done
					}
					inputs[0] = *input
				} else {
					result <- inputs[1]

					input, done, err := getCommit(inputs[1].Parent)
					if err != nil {
						return
					} else if done {
						return // no more new parent --> we are done
					}
					inputs[1] = *input
				}
			}
		}
	}()

	return result, nil
}

func getCommit(commit string) (*Commit, bool, error) {
	if commit == "" {
		return nil, true, nil // we are done
	}
	co, err := core.GetCommitObject(commit)
	if err != nil {
		return nil, false, err
	}
	result := Commit{Hash: commit, Message: co.S3gitMessage, TimeStamp: co.S3gitTimeStamp}
	if len(co.S3gitWarmParents) == 1 {
		result.Parent = co.S3gitWarmParents[0]
	} else if len(co.S3gitWarmParents) > 1 {
		// TODO: Add other parents to inputs
		result.Parent = co.S3gitWarmParents[0]
	}
	return &result, false, err
}
<|endoftext|>


<commit_before>// Copyright (c) 2013 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package yubikey

import (
	"bytes"
	"encoding/hex"
	"reflect"
	"testing"
)

var aesEncodeTests = []struct {
	buf string
	key string
	out string
}{
	{
		"0123456789abcdef",
		"abcdef0123456789",
		"\x83\x8a\x46\x7f\x34\x63\x95\x51\x75\x5b\xd3\x2a\x4a\x2f\x15\xe1",
	},
	{
		"\x69\xb6\x48\x1c\x8b\xab\xa2\xb6\x0e\x8f\x22\x17\x9b\x58\xcd\x56",
		string([]byte{
			0xec, 0xde, 0x18, 0xdb, 0xe7, 0x6f, 0xbd, 0x0c,
			0x33, 0x33, 0x0f, 0x1c, 0x35, 0x48, 0x71, 0xdb,
		}),
		"\x87\x92\xeb\xfe\x26\xcc\x13\x00\x30\xc2\x00\x11\xc8\x9f\x23\xc8",
	},
}

var crcTests = []struct {
	in  []byte
	out uint16
}{
	{
		[]byte{
			0x87, 0x92, 0xeb, 0xfe, 0x26, 0xcc, 0x13, 0x00,
			0x30, 0xc2, 0x00, 0x11, 0xc8, 0x9f, 0x23, 0xc8,
		},
		61624,
	},
}

var hexEncodeTests = []struct {
	in  string
	out string
}{
	{"test", "74657374"},
}

var hexPTests = []struct {
	in  string
	out bool
}{
	{"0123456789abcdef", true},
	{"6789abcdefghijkl", false},
}

var modHexEncodeTests = []struct {
	in  string
	out string
}{
	{"test", "ifhgieif"},
	{"justanothergotest", "hligieifhbhuhvifhjhgidhihvifhgieif"},
	{"foobar", "hhhvhvhdhbid"},
}

var modHexPTests = []struct {
	in  string
	out bool
}{
	{"cbdefghijklnrtuv", true},
	{"0123Xabc", false},
}

var parseTests = []struct {
	token  string
	key    string
	result []byte
}{
	{
		"dcflcindvdbrblehecuitvjkjevvehjd",
		string([]byte{
			0xec, 0xde, 0x18, 0xdb, 0xe7, 0x6f, 0xbd, 0x0c,
			0x33, 0x33, 0x0f, 0x1c, 0x35, 0x48, 0x71, 0xdb,
		}),
		[]byte{
			0x87, 0x92, 0xeb, 0xfe, 0x26, 0xcc, 0x13, 0x00,
			0xa8, 0xc0, 0x00, 0x10, 0xb4, 0x08, 0x6f, 0x5b,
		},
	},
	{
		"hknhfjbrjnlnldnhcujvddbikngjrtgh",
		string([]byte{
			0xec, 0xde, 0x18, 0xdb, 0xe7, 0x6f, 0xbd, 0x0c,
			0x33, 0x33, 0x0f, 0x1c, 0x35, 0x48, 0x71, 0xdb,
		}),
		[]byte{
			0x87, 0x92, 0xeb, 0xfe, 0x26, 0xcc, 0x13, 0x00,
			0x30, 0xc2, 0x00, 0x11, 0xc8, 0x9f, 0x23, 0xc8,
		},
	},
}

var otpTests = []struct {
	token    []byte
	key      []byte
	capslock bool
	counter  bool
	crc16    bool
}{
	{
		[]byte{
			0x16, 0xe1, 0xe5, 0xd9, 0xd3, 0x99, 0x10, 0x04,
			0x45, 0x20, 0x07, 0xe3, 0x02, 0x00, 0x22, 0x6d,
		},
		[]byte{
			0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x30, 0x31,
			0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
		},
		false,
		false,
		true,
	},
	{
		[]byte{
			0x16, 0xe1, 0xe5, 0xd9, 0xd3, 0x99, 0x81, 0xab,
			0x45, 0x20, 0x07, 0xe3, 0x02, 0x00, 0x6d, 0x80,
		},
		[]byte{
			0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x30, 0x31,
			0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
		},
		true,
		false,
		true,
	},
	{
		[]byte{
			0x16, 0xe1, 0xe5, 0xd9, 0xd3, 0x99, 0xff, 0x7f,
			0x45, 0x20, 0x07, 0xe3, 0x02, 0x00, 0xbd, 0xa3,
		},
		[]byte{
			0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x30, 0x31,
			0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
		},
		false,
		true,
		true,
	},
}

func TestAes(t *testing.T) {
	// Encrypt and Decrypt tests
	for x, test := range aesEncodeTests {
		// Encrypt tests
		key := NewKey(test.key)
		if res := AesEncrypt([]byte(test.out), key); !bytes.Equal(res, []byte(test.buf)) {
			t.Errorf("AesEncrypt test #%d failed: got: %s want: %s",
				x, res, test.buf)
			continue
		}

		// Decrypt tests
		if res := AesDecrypt([]byte(test.buf), key); !bytes.Equal(res, []byte(test.out)) {
			t.Errorf("AesDecrypt test #%d failed: got: %s want: %s",
				x, res, test.out)
			continue
		}
	}
}

func TestCapslock(t *testing.T) {
	for x, test := range otpTests {
		token, err := NewTokenFromBytes(test.token)
		if err != nil {
			t.Errorf("Capslock test #%d failed: %v", x, err)
			continue
		}

		res := token.Capslock() == 0x8000
		if res != test.capslock {
			t.Errorf("Capslock test #%d failed: got: %v want: %v",
				x, res, test.capslock)
		}

	}
}

func TestCounter(t *testing.T) {
	for x, test := range otpTests {
		token, err := NewTokenFromBytes(test.token)
		if err != nil {
			t.Errorf("Counter test #%d failed: %v", x, err)
			continue
		}

		res := token.Counter() == 0x7fff
		if res != test.counter {
			t.Errorf("Counter test #%d failed: got: %v want: %v",
				x, res, test.counter)
		}
	}
}

func TestCrc(t *testing.T) {
	for x, test := range crcTests {
		token, err := NewTokenFromBytes(test.in)
		if err != nil {
			t.Errorf("Crc16 test #%d failed: %v\n", err)
			continue
		}

		if res := token.Crc16(); res != test.out {
			t.Errorf("Crc16 test #%d failed: got: %v want: %v",
				x, res, test.out)
			continue
		}
	}
}

func TestHex(t *testing.T) {
	// Encode and Decode tests
	for x, test := range hexEncodeTests {
		// Encode tests
		if res := HexEncode(test.in); res != test.out {
			t.Errorf("HexEncode test #%d failed: got: %s want: %s",
				x, res, test.out)
			continue
		}
		// Decode tests
		if res := HexDecode(test.out); res != test.in {
			t.Errorf("HexDecode test #%d failed: got: %s want: %s",
				x, res, test.in)
			continue
		}

	}
	for x, test := range hexPTests {
		tmp := []byte(test.in)
		if res := HexP(tmp); res != test.out {
			t.Errorf("HexP test #%d failed: got: %v want: %v",
				x, res, test.out)
			continue
		}
	}
}

func TestModHex(t *testing.T) {
	// Encode and Decode tests
	for x, test := range modHexEncodeTests {
		// Encode tests
		if res := ModHexEncode([]byte(test.in)); !bytes.Equal(res, []byte(test.out)) {
			t.Errorf("ModeHexEncode test #%d failed: got: %s want: %s",
				x, res, test.out)
			continue
		}
		// Decode tests
		if res := ModHexDecode([]byte(test.out)); !bytes.Equal(res, []byte(test.in)) {
			t.Errorf("ModeHexDecode test #%d failed: got: %v want: %v",
				x, res, test.in)
			continue
		}
	}

	// ModHexP tests
	for x, test := range modHexPTests {
		tmp := []byte(test.in)
		if res := ModHexP(tmp); res != test.out {
			t.Errorf("ModeHexP test #%d failed: got: %v want: %v",
				x, res, test.out)
			continue
		}
	}
}

func TestParse(t *testing.T) {
	for x, test := range parseTests {
		otp := NewOtp(test.token)
		key := NewKey(test.key)
		res, err := otp.Parse(key)
		if err != nil {
			t.Errorf("TestParse test #%d failed: %v", x, err)
			continue
		}

		buf := res.Bytes()

		if !bytes.Equal(buf, test.result) {
			t.Errorf("TestParse test #%d failed: got: %x want: %x",
				x, buf, test.result)
			continue
		}
	}
}

func TestOtp(t *testing.T) {
	for x, test := range otpTests {
		token, err := NewTokenFromBytes(test.token)
		if err != nil {
			t.Errorf("TestOtp test #%d failed: %v", x, err)
			continue
		}
		key := NewKey(string(test.key))
		otp := token.Generate(key)

		res, err := otp.Parse(key)
		if err != nil {
			t.Errorf("TestOtp test #%d failed: %v", x, err)
			continue
		}
		buf := res.Bytes()

		if !bytes.Equal(test.token, buf) {
			t.Errorf("TestOtp test #%d failed: got: %v want: %v",
				x, test.token, buf)
			continue
		}

	}
}

func TestNewToken(t *testing.T) {

	// sample key from test-vectors.txt
	uid, _ := hex.DecodeString("8792ebfe26cc")

	token := NewToken(NewUid(string(uid)), 19, 49712, 0, 17, 40904)

	var u Uid
	copy(u[:], uid)

	tok := &Token{Uid: u, Ctr: 19, Tstpl: 49712, Tstph: 0, Use: 17, Rnd: 40904, Crc: 51235}

	if !reflect.DeepEqual(tok, token) {
		t.Errorf("TestNewToken failed: got=%#v wanted=%#v", token, tok)
	}
}
<commit_msg>A small formatting string bug from go-vet<commit_after>// Copyright (c) 2013 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package yubikey

import (
	"bytes"
	"encoding/hex"
	"reflect"
	"testing"
)

var aesEncodeTests = []struct {
	buf string
	key string
	out string
}{
	{
		"0123456789abcdef",
		"abcdef0123456789",
		"\x83\x8a\x46\x7f\x34\x63\x95\x51\x75\x5b\xd3\x2a\x4a\x2f\x15\xe1",
	},
	{
		"\x69\xb6\x48\x1c\x8b\xab\xa2\xb6\x0e\x8f\x22\x17\x9b\x58\xcd\x56",
		string([]byte{
			0xec, 0xde, 0x18, 0xdb, 0xe7, 0x6f, 0xbd, 0x0c,
			0x33, 0x33, 0x0f, 0x1c, 0x35, 0x48, 0x71, 0xdb,
		}),
		"\x87\x92\xeb\xfe\x26\xcc\x13\x00\x30\xc2\x00\x11\xc8\x9f\x23\xc8",
	},
}

var crcTests = []struct {
	in  []byte
	out uint16
}{
	{
		[]byte{
			0x87, 0x92, 0xeb, 0xfe, 0x26, 0xcc, 0x13, 0x00,
			0x30, 0xc2, 0x00, 0x11, 0xc8, 0x9f, 0x23, 0xc8,
		},
		61624,
	},
}

var hexEncodeTests = []struct {
	in  string
	out string
}{
	{"test", "74657374"},
}

var hexPTests = []struct {
	in  string
	out bool
}{
	{"0123456789abcdef", true},
	{"6789abcdefghijkl", false},
}

var modHexEncodeTests = []struct {
	in  string
	out string
}{
	{"test", "ifhgieif"},
	{"justanothergotest", "hligieifhbhuhvifhjhgidhihvifhgieif"},
	{"foobar", "hhhvhvhdhbid"},
}

var modHexPTests = []struct {
	in  string
	out bool
}{
	{"cbdefghijklnrtuv", true},
	{"0123Xabc", false},
}

var parseTests = []struct {
	token  string
	key    string
	result []byte
}{
	{
		"dcflcindvdbrblehecuitvjkjevvehjd",
		string([]byte{
			0xec, 0xde, 0x18, 0xdb, 0xe7, 0x6f, 0xbd, 0x0c,
			0x33, 0x33, 0x0f, 0x1c, 0x35, 0x48, 0x71, 0xdb,
		}),
		[]byte{
			0x87, 0x92, 0xeb, 0xfe, 0x26, 0xcc, 0x13, 0x00,
			0xa8, 0xc0, 0x00, 0x10, 0xb4, 0x08, 0x6f, 0x5b,
		},
	},
	{
		"hknhfjbrjnlnldnhcujvddbikngjrtgh",
		string([]byte{
			0xec, 0xde, 0x18, 0xdb, 0xe7, 0x6f, 0xbd, 0x0c,
			0x33, 0x33, 0x0f, 0x1c, 0x35, 0x48, 0x71, 0xdb,
		}),
		[]byte{
			0x87, 0x92, 0xeb, 0xfe, 0x26, 0xcc, 0x13, 0x00,
			0x30, 0xc2, 0x00, 0x11, 0xc8, 0x9f, 0x23, 0xc8,
		},
	},
}

var otpTests = []struct {
	token    []byte
	key      []byte
	capslock bool
	counter  bool
	crc16    bool
}{
	{
		[]byte{
			0x16, 0xe1, 0xe5, 0xd9, 0xd3, 0x99, 0x10, 0x04,
			0x45, 0x20, 0x07, 0xe3, 0x02, 0x00, 0x22, 0x6d,
		},
		[]byte{
			0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x30, 0x31,
			0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
		},
		false,
		false,
		true,
	},
	{
		[]byte{
			0x16, 0xe1, 0xe5, 0xd9, 0xd3, 0x99, 0x81, 0xab,
			0x45, 0x20, 0x07, 0xe3, 0x02, 0x00, 0x6d, 0x80,
		},
		[]byte{
			0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x30, 0x31,
			0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
		},
		true,
		false,
		true,
	},
	{
		[]byte{
			0x16, 0xe1, 0xe5, 0xd9, 0xd3, 0x99, 0xff, 0x7f,
			0x45, 0x20, 0x07, 0xe3, 0x02, 0x00, 0xbd, 0xa3,
		},
		[]byte{
			0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x30, 0x31,
			0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
		},
		false,
		true,
		true,
	},
}

func TestAes(t *testing.T) {
	// Encrypt and Decrypt tests
	for x, test := range aesEncodeTests {
		// Encrypt tests
		key := NewKey(test.key)
		if res := AesEncrypt([]byte(test.out), key); !bytes.Equal(res, []byte(test.buf)) {
			t.Errorf("AesEncrypt test #%d failed: got: %s want: %s",
				x, res, test.buf)
			continue
		}

		// Decrypt tests
		if res := AesDecrypt([]byte(test.buf), key); !bytes.Equal(res, []byte(test.out)) {
			t.Errorf("AesDecrypt test #%d failed: got: %s want: %s",
				x, res, test.out)
			continue
		}
	}
}

func TestCapslock(t *testing.T) {
	for x, test := range otpTests {
		token, err := NewTokenFromBytes(test.token)
		if err != nil {
			t.Errorf("Capslock test #%d failed: %v", x, err)
			continue
		}

		res := token.Capslock() == 0x8000
		if res != test.capslock {
			t.Errorf("Capslock test #%d failed: got: %v want: %v",
				x, res, test.capslock)
		}

	}
}

func TestCounter(t *testing.T) {
	for x, test := range otpTests {
		token, err := NewTokenFromBytes(test.token)
		if err != nil {
			t.Errorf("Counter test #%d failed: %v", x, err)
			continue
		}

		res := token.Counter() == 0x7fff
		if res != test.counter {
			t.Errorf("Counter test #%d failed: got: %v want: %v",
				x, res, test.counter)
		}
	}
}

func TestCrc(t *testing.T) {
	for x, test := range crcTests {
		token, err := NewTokenFromBytes(test.in)
		if err != nil {
			t.Errorf("Crc16 test #%d failed: %v\n", x, err)
			continue
		}

		if res := token.Crc16(); res != test.out {
			t.Errorf("Crc16 test #%d failed: got: %v want: %v",
				x, res, test.out)
			continue
		}
	}
}

func TestHex(t *testing.T) {
	// Encode and Decode tests
	for x, test := range hexEncodeTests {
		// Encode tests
		if res := HexEncode(test.in); res != test.out {
			t.Errorf("HexEncode test #%d failed: got: %s want: %s",
				x, res, test.out)
			continue
		}
		// Decode tests
		if res := HexDecode(test.out); res != test.in {
			t.Errorf("HexDecode test #%d failed: got: %s want: %s",
				x, res, test.in)
			continue
		}

	}
	for x, test := range hexPTests {
		tmp := []byte(test.in)
		if res := HexP(tmp); res != test.out {
			t.Errorf("HexP test #%d failed: got: %v want: %v",
				x, res, test.out)
			continue
		}
	}
}

func TestModHex(t *testing.T) {
	// Encode and Decode tests
	for x, test := range modHexEncodeTests {
		// Encode tests
		if res := ModHexEncode([]byte(test.in)); !bytes.Equal(res, []byte(test.out)) {
			t.Errorf("ModeHexEncode test #%d failed: got: %s want: %s",
				x, res, test.out)
			continue
		}
		// Decode tests
		if res := ModHexDecode([]byte(test.out)); !bytes.Equal(res, []byte(test.in)) {
			t.Errorf("ModeHexDecode test #%d failed: got: %v want: %v",
				x, res, test.in)
			continue
		}
	}

	// ModHexP tests
	for x, test := range modHexPTests {
		tmp := []byte(test.in)
		if res := ModHexP(tmp); res != test.out {
			t.Errorf("ModeHexP test #%d failed: got: %v want: %v",
				x, res, test.out)
			continue
		}
	}
}

func TestParse(t *testing.T) {
	for x, test := range parseTests {
		otp := NewOtp(test.token)
		key := NewKey(test.key)
		res, err := otp.Parse(key)
		if err != nil {
			t.Errorf("TestParse test #%d failed: %v", x, err)
			continue
		}

		buf := res.Bytes()

		if !bytes.Equal(buf, test.result) {
			t.Errorf("TestParse test #%d failed: got: %x want: %x",
				x, buf, test.result)
			continue
		}
	}
}

func TestOtp(t *testing.T) {
	for x, test := range otpTests {
		token, err := NewTokenFromBytes(test.token)
		if err != nil {
			t.Errorf("TestOtp test #%d failed: %v", x, err)
			continue
		}
		key := NewKey(string(test.key))
		otp := token.Generate(key)

		res, err := otp.Parse(key)
		if err != nil {
			t.Errorf("TestOtp test #%d failed: %v", x, err)
			continue
		}
		buf := res.Bytes()

		if !bytes.Equal(test.token, buf) {
			t.Errorf("TestOtp test #%d failed: got: %v want: %v",
				x, test.token, buf)
			continue
		}

	}
}

func TestNewToken(t *testing.T) {

	// sample key from test-vectors.txt
	uid, _ := hex.DecodeString("8792ebfe26cc")

	token := NewToken(NewUid(string(uid)), 19, 49712, 0, 17, 40904)

	var u Uid
	copy(u[:], uid)

	tok := &Token{Uid: u, Ctr: 19, Tstpl: 49712, Tstph: 0, Use: 17, Rnd: 40904, Crc: 51235}

	if !reflect.DeepEqual(tok, token) {
		t.Errorf("TestNewToken failed: got=%#v wanted=%#v", token, tok)
	}
}
<|endoftext|>


<commit_before>package p5r

import (
	goregexp "regexp"
)

// Compatability functions for the "regexp" package

// MatchString return true if the string matches the regex
// Returns false if an error/timeout occurs
func (re *Regexp) MatchString(s string) bool {
	m, err := re.run(true, -1, getRunes(s))
	if err != nil {
		return false
	}
	return m != nil
}

// ReplaceAllString returns a modified string if the replacement worked
// Returns the original string if an error occured
func (re *Regexp) ReplaceAllString(input, replacement string) string {
	output, err := re.Replace(input, replacement, -1, -1)
	if err != nil {
		// Return the original string if something went wrong
		return input
	}
	// Return the string with replacements
	return output
}

// Compile a string to a Regexp, returns an error if something went wrong
func Compile(input string) (*Regexp, error) {
	return Compile2(input, 0)
}

// Compile a string to a Regexp
func MustCompile(input string) *Regexp {
	return MustCompile2(input, 0)
}

// Convert a p5r.Regex to a regexp.Regexp
func (re *Regexp) Convert() (*goregexp.Regexp, error) {
	return goregexp.Compile(re.pattern)
}

// Convert a p5r.Regex to a regexp.Regexp
func (re *Regexp) MustConvert() *goregexp.Regexp {
	return goregexp.MustCompile(re.pattern)
}
<commit_msg>Add QuoteMeta<commit_after>package p5r

import (
	goregexp "regexp"
)

// Compatability functions for the "regexp" package

// MatchString return true if the string matches the regex
// Returns false if an error/timeout occurs
func (re *Regexp) MatchString(s string) bool {
	m, err := re.run(true, -1, getRunes(s))
	if err != nil {
		return false
	}
	return m != nil
}

// ReplaceAllString returns a modified string if the replacement worked
// Returns the original string if an error occured
func (re *Regexp) ReplaceAllString(input, replacement string) string {
	output, err := re.Replace(input, replacement, -1, -1)
	if err != nil {
		// Return the original string if something went wrong
		return input
	}
	// Return the string with replacements
	return output
}

// Compile a string to a Regexp, returns an error if something went wrong
func Compile(input string) (*Regexp, error) {
	return Compile2(input, 0)
}

// Compile a string to a Regexp
func MustCompile(input string) *Regexp {
	return MustCompile2(input, 0)
}

// Convert a p5r.Regex to a regexp.Regexp
func (re *Regexp) Convert() (*goregexp.Regexp, error) {
	return goregexp.Compile(re.pattern)
}

// Convert a p5r.Regex to a regexp.Regexp
func (re *Regexp) MustConvert() *goregexp.Regexp {
	return goregexp.MustCompile(re.pattern)
}

func QuoteMeta(s string) string {
	return goregexp.QuoteMeta(s)
}
<|endoftext|>


<commit_before>// goncurses - ncurses library for Go.
// Copyright 2011 Rob Thornton. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package goncurses

// #include <stdlib.h>
// #include <ncurses.h>
import "C"

import (
	"errors"
	"unsafe"
)

type SlkFormat byte

const (
	SLK_323        SlkFormat = iota // 8 labels; 3-2-3 arrangement
	SLK_44                          // 8 labels; 4-4 arrangement
	SLK_PC444                       // 12 labels; 4-4-4
	SLK_PC444INDEX                  // 12 labels; 4-4-4 arrangement with index line
)

type SlkJustify byte

const (
	SLK_LEFT SlkJustify = iota
	SLK_CENTER
	SLK_RIGHT
)

// Initializes the soft-key labels with the given format; keys like the
// F1-F12 keys on most keyboards. After a call to SlkRefresh a bar at the
// bottom of the standard screen returned by Init will be displayed. This
// function MUST be called prior to Init()
func SlkInit(f SlkFormat) {
	C.slk_init(C.int(f))
}

// SlkSet sets the 'labnum' text to the supplied 'label'. Labels must not
// be greater than 8 characters
func SlkSet(labnum int, label string, just SlkJustify) error {
	cstr := C.CString(label)
	defer C.free(unsafe.Pointer(cstr))

	if C.slk_set(C.int(labnum), (*C.char)(cstr), C.int(just)) == C.ERR {
		return errors.New("Soft-keys not initialized")
	}
	return nil
}

// SlkRefresh behaves the same as Window.Refresh. Most applications would use
// SlkNoutRefresh because a Window.Refresh is likely to follow
func SlkRefresh() error {
	if C.slk_refresh() == C.ERR {
		return errors.New("Soft-keys not initialized.")
	}
	return nil
}

// SlkNoutFresh behaves like Window.NoutRefresh
func SlkNoutRefresh() error {
	if C.slk_noutrefresh() == C.ERR {
		return errors.New("Soft-keys not initialized.")
	}
	return nil
}

// SlkLabel returns the label for the given key
func SlkLabel(labnum int) string {
	return C.GoString(C.slk_label(C.int(labnum)))
}

// SlkClear removes the soft-key labels from the screen
func SlkClear() error {
	if C.slk_clear() == C.ERR {
		return errors.New("Soft-keys not initialized.")
	}
	return nil
}

// SlkRestore restores the soft-key labels to the screen after an SlkClear()
func SlkRestore() error {
	if C.slk_restore() == C.ERR {
		return errors.New("Soft-keys not initialized.")
	}
	return nil
}

// SlkTouch behaves just like Window.Touch
func SlkTouch() error {
	if C.slk_touch() == C.ERR {
		return errors.New("Soft-keys not initialized.")
	}
	return nil
}

// SlkColor sets the color pair for the soft-keys
func SlkColor(cp int16) error {
	if C.slk_color(C.short(cp)) == C.ERR {
		return errors.New("Invalid color pair or soft-keys not initialized.")
	}
	return nil
}

// SlkSetAttribute turns attributes on or off
//func SlkSetAttribute(attr Char, on bool) {}
<commit_msg>Implement Slk Attributes, fix some error messages and fix formatting on constant godoc<commit_after>// goncurses - ncurses library for Go.
// Copyright 2011 Rob Thornton. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package goncurses

// #include <stdlib.h>
// #include <ncurses.h>
import "C"

import (
	"errors"
	"unsafe"
)

type SlkFormat byte

const (
	SLK_323        SlkFormat = iota // 8 labels; 3-2-3 arrangement
	SLK_44                          // 8 labels; 4-4 arrangement
	SLK_PC444                       // 12 labels; 4-4-4 arrangement
	SLK_PC444INDEX                  // 12 labels; 4-4-4 with index line
)

type SlkJustify byte

const (
	SLK_LEFT SlkJustify = iota
	SLK_CENTER
	SLK_RIGHT
)

// Initializes the soft-key labels with the given format; keys like the
// F1-F12 keys on most keyboards. After a call to SlkRefresh a bar at the
// bottom of the standard screen returned by Init will be displayed. This
// function MUST be called prior to Init()
func SlkInit(f SlkFormat) {
	C.slk_init(C.int(f))
}

// SlkSet sets the 'labnum' text to the supplied 'label'. Labels must not
// be greater than 8 characters
func SlkSet(labnum int, label string, just SlkJustify) error {
	cstr := C.CString(label)
	defer C.free(unsafe.Pointer(cstr))

	if C.slk_set(C.int(labnum), (*C.char)(cstr), C.int(just)) == C.ERR {
		return errors.New("Soft-keys or terminal not initialized")
	}
	return nil
}

// SlkRefresh behaves the same as Window.Refresh. Most applications would use
// SlkNoutRefresh because a Window.Refresh is likely to follow
func SlkRefresh() error {
	if C.slk_refresh() == C.ERR {
		return errors.New("Soft-keys or terminal not initialized.")
	}
	return nil
}

// SlkNoutFresh behaves like Window.NoutRefresh
func SlkNoutRefresh() error {
	if C.slk_noutrefresh() == C.ERR {
		return errors.New("Soft-keys or terminal not initialized.")
	}
	return nil
}

// SlkLabel returns the label for the given key
func SlkLabel(labnum int) string {
	return C.GoString(C.slk_label(C.int(labnum)))
}

// SlkClear removes the soft-key labels from the screen
func SlkClear() error {
	if C.slk_clear() == C.ERR {
		return errors.New("Soft-keys or terminal not initialized.")
	}
	return nil
}

// SlkRestore restores the soft-key labels to the screen after an SlkClear()
func SlkRestore() error {
	if C.slk_restore() == C.ERR {
		return errors.New("Soft-keys or terminal not initialized.")
	}
	return nil
}

// SlkTouch behaves just like Window.Touch
func SlkTouch() error {
	if C.slk_touch() == C.ERR {
		return errors.New("Soft-keys or terminal not initialized.")
	}
	return nil
}

// SlkColor sets the color pair for the soft-keys
func SlkColor(cp int16) error {
	if C.slk_color(C.short(cp)) == C.ERR {
		return errors.New("Invalid color pair or soft-keys not initialized.")
	}
	return nil
}

// SlkAttribute returns the currently set attributes
func SlkAttribute() Char {
	return Char(C.slk_attr())
}

// SlkSetAttribute sets the OR'd attributes to use
func SlkSetAttribute(attr Char) error {
	if C.slk_attrset(C.chtype(attr)) == C.ERR {
		return errors.New("Soft-keys or terminal not initialized.")
	}
	return nil
}

// SlkAttributeOn turns on the given OR'd attributes without turning any off
func SlkAttributeOn(attr Char) error {
	if C.slk_attron(C.chtype(attr)) == C.ERR {
		return errors.New("Soft-keys or terminal not initialized.")
	}
	return nil
}

// SlkAttributeOff turns off the given OR'd attributes withoiut turning any on
func SlkAttributeOff(attr Char) error {
	if C.slk_attroff(C.chtype(attr)) == C.ERR {
		return errors.New("Soft-keys or terminal not initialized.")
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"io"
	"os"
	"regexp"
	"strings"
	"unicode/utf8"

	"github.com/edsrzf/mmap-go"
	"github.com/mibk/syd/event"
	"github.com/mibk/syd/text"
	"github.com/mibk/syd/ui/console"
	"github.com/mibk/syd/view"
)

var (
	ui       console.Console
	filename = ""

	textBuf  *text.Text
	viewport *view.View
)

func main() {
	ui.Init()
	defer ui.Close()

	var initContent []byte
	if len(os.Args) > 1 {
		filename = os.Args[1]
		m, err := readFile(filename)
		if err != nil {
			panic(err)
		}
		defer m.Unmap()
		initContent = []byte(m)
	} else {
		initContent = []byte("\n")
	}

	textBuf = text.New(initContent)
	viewport = view.New(textBuf)
	normalMode()
}

func readFile(filename string) (mmap.MMap, error) {
	f, err := os.Open(filename)
	if err != nil {
		return nil, err
	}

	m, err := mmap.Map(f, 0, 0)
	if err != nil {
		return nil, err
	}
	return m, nil
}

func normalMode() {
Loop:
	for {
		viewport.Draw(ui)
		printFoot()
		ui.Flush()
		ev := event.PollEvent()
		switch ev := ev.(type) {
		case event.KeyPress:
			switch ev.Key {
			case 'j':
				viewport.MoveDown()
			case 'k':
				viewport.MoveUp()
			case 'h':
				viewport.MoveLeft()
			case 'l':
				viewport.MoveRight()
			case 'q':
				break Loop
			case 'u':
				textBuf.Undo()
			case 'r':
				if ev.Ctrl {
					textBuf.Redo()
				}

			case 'i':
				insertMode()
			case ':':
				commandMode()
			}

		}
	}
}

func insertMode() {
	for {
		viewport.Draw(ui)
		_, h := ui.Size()
		printFoot()
		print(0, h-1, "-- INSERT --", console.AttrBold)
		ui.Flush()
		ev := event.PollEvent()
		switch ev := ev.(type) {
		case event.KeyPress:
			switch ev.Key {
			case event.Escape:
				textBuf.CommitChanges()
				return
			case event.Backspace:
				viewport.MoveLeft()
				fallthrough
			case event.Delete:
				c := viewport.CurrentCell()
				length := utf8.RuneLen(c.R)
				textBuf.Delete(c.Off, length)
			case event.Enter:
				textBuf.Insert(viewport.CurrentCell().Off, []byte("\n"))
				viewport.ReadLines()
				viewport.MoveDown()
				viewport.ToTheStartColumn()
			default:
				buf := make([]byte, 4)
				n := utf8.EncodeRune(buf, rune(ev.Key))
				textBuf.Insert(viewport.CurrentCell().Off, buf[:n])
				viewport.ReadLines()
				viewport.MoveRight()
			}
		}
	}
}

func commandMode() {
	cmd := make([]rune, 0, 20)
	cur := 0
Loop:
	for {
		viewport.Draw(ui)
		printFoot()
		_, h := ui.Size()
		print(0, h-1, ":"+string(cmd), console.AttrDefault)
		ui.SetCursor(cur+1, h-1)
		ui.Flush()
		ev := event.PollEvent()
		switch ev := ev.(type) {
		case event.KeyPress:
			switch ev.Key {
			case event.Escape:
				return
			case event.Backspace:
				if cur > 0 {
					cur--
					cmd = cmd[:cur]
				}
			case event.Enter:
				break Loop
			default:
				cmd = append(cmd, rune(ev.Key))
				cur++
			}
		}
	}
	exec(string(cmd))
}

var writeRE = regexp.MustCompile(`w( .+)?`)

func exec(cmd string) {
	if match := writeRE.FindStringSubmatch(cmd); match != nil {
		if match[1] != "" {
			filename = strings.Trim(match[1], " \t")
		}
		if filename == "" {
			_, h := ui.Size()
			print(0, h-1, "no filename! (press any key)", console.AttrDefault)
			ui.Flush()
			event.PollEvent()
		} else {
			if err := saveFile(filename); err != nil {
				panic(err)
			}
		}
	}
}

func saveFile(filename string) error {
	const bufSize = 2048
	textBuf.Save()
	tmpFile := filename + "~"
	f, err := os.Create(tmpFile)
	if err != nil {
		return err
	}
	defer f.Close()
	buf := make([]byte, bufSize)

	for off := int64(0); ; off += bufSize {
		n, err := textBuf.ReadAt(buf, off)
		if err != nil && err != io.EOF {
			return err
		}
		if _, err := f.Write(buf[:n]); err != nil {
			return err
		}
		if err == io.EOF {
			break
		}
	}
	if err := os.Rename(tmpFile, filename); err != nil {
		return err
	}
	return nil
}

func print(x, y int, s string, attrs uint8) {
	for _, r := range []rune(s) {
		ui.SetCell(x, y, r, attrs)
		x++
	}
}

func printFoot() {
	w, h := ui.Size()
	for x := 0; x < w; x++ {
		ui.SetCell(x, h-2, ' ', console.AttrReverse|console.AttrBold)
	}
	filename := filename
	if filename == "" {
		filename = "[No Name]"
	}
	if textBuf.Modified() {
		filename += " [+]"
	}
	print(0, h-2, filename, console.AttrReverse|console.AttrBold)
}
<commit_msg>Change a way how to save a file<commit_after>package main

import (
	"io"
	"os"
	"regexp"
	"strings"
	"unicode/utf8"

	"github.com/edsrzf/mmap-go"
	"github.com/mibk/syd/event"
	"github.com/mibk/syd/text"
	"github.com/mibk/syd/ui/console"
	"github.com/mibk/syd/view"
)

var (
	ui       console.Console
	filename = ""

	textBuf  *text.Text
	viewport *view.View
)

func main() {
	ui.Init()
	defer ui.Close()

	var initContent []byte
	if len(os.Args) > 1 {
		filename = os.Args[1]
		m, err := readFile(filename)
		if err != nil {
			panic(err)
		}
		defer m.Unmap()
		initContent = []byte(m)
	} else {
		initContent = []byte("\n")
	}

	textBuf = text.New(initContent)
	viewport = view.New(textBuf)
	normalMode()
}

func readFile(filename string) (mmap.MMap, error) {
	f, err := os.Open(filename)
	if err != nil {
		return nil, err
	}

	m, err := mmap.Map(f, 0, 0)
	if err != nil {
		return nil, err
	}
	return m, nil
}

func normalMode() {
Loop:
	for {
		viewport.Draw(ui)
		printFoot()
		ui.Flush()
		ev := event.PollEvent()
		switch ev := ev.(type) {
		case event.KeyPress:
			switch ev.Key {
			case 'j':
				viewport.MoveDown()
			case 'k':
				viewport.MoveUp()
			case 'h':
				viewport.MoveLeft()
			case 'l':
				viewport.MoveRight()
			case 'q':
				break Loop
			case 'u':
				textBuf.Undo()
			case 'r':
				if ev.Ctrl {
					textBuf.Redo()
				}

			case 'i':
				insertMode()
			case ':':
				commandMode()
			}

		}
	}
}

func insertMode() {
	for {
		viewport.Draw(ui)
		_, h := ui.Size()
		printFoot()
		print(0, h-1, "-- INSERT --", console.AttrBold)
		ui.Flush()
		ev := event.PollEvent()
		switch ev := ev.(type) {
		case event.KeyPress:
			switch ev.Key {
			case event.Escape:
				textBuf.CommitChanges()
				return
			case event.Backspace:
				viewport.MoveLeft()
				fallthrough
			case event.Delete:
				c := viewport.CurrentCell()
				length := utf8.RuneLen(c.R)
				textBuf.Delete(c.Off, length)
			case event.Enter:
				textBuf.Insert(viewport.CurrentCell().Off, []byte("\n"))
				viewport.ReadLines()
				viewport.MoveDown()
				viewport.ToTheStartColumn()
			default:
				buf := make([]byte, 4)
				n := utf8.EncodeRune(buf, rune(ev.Key))
				textBuf.Insert(viewport.CurrentCell().Off, buf[:n])
				viewport.ReadLines()
				viewport.MoveRight()
			}
		}
	}
}

func commandMode() {
	cmd := make([]rune, 0, 20)
	cur := 0
Loop:
	for {
		viewport.Draw(ui)
		printFoot()
		_, h := ui.Size()
		print(0, h-1, ":"+string(cmd), console.AttrDefault)
		ui.SetCursor(cur+1, h-1)
		ui.Flush()
		ev := event.PollEvent()
		switch ev := ev.(type) {
		case event.KeyPress:
			switch ev.Key {
			case event.Escape:
				return
			case event.Backspace:
				if cur > 0 {
					cur--
					cmd = cmd[:cur]
				}
			case event.Enter:
				break Loop
			default:
				cmd = append(cmd, rune(ev.Key))
				cur++
			}
		}
	}
	exec(string(cmd))
}

var writeRE = regexp.MustCompile(`w( .+)?`)

func exec(cmd string) {
	if match := writeRE.FindStringSubmatch(cmd); match != nil {
		if match[1] != "" {
			filename = strings.Trim(match[1], " \t")
		}
		if filename == "" {
			_, h := ui.Size()
			print(0, h-1, "no filename! (press any key)", console.AttrDefault)
			ui.Flush()
			event.PollEvent()
		} else {
			if err := saveFile(filename); err != nil {
				panic(err)
			}
		}
	}
}

func saveFile(filename string) error {
	textBuf.Save()
	tmpFile := filename + "~"
	f, err := os.Create(tmpFile)
	if err != nil {
		return err
	}
	io.Copy(f, ReaderFrom(textBuf, 0))
	f.Close()

	if err := os.Rename(tmpFile, filename); err != nil {
		return err
	}
	return nil
}

// ReaderFrom accepts an io.ReaderAt and returns the io.Reader
// that can read from the offset to the EOF.
func ReaderFrom(r io.ReaderAt, offset int64) io.Reader {
	return &Reader{r, offset}
}

type Reader struct {
	readerAt io.ReaderAt
	off      int64
}

func (r *Reader) Read(data []byte) (n int, err error) {
	n, err = r.readerAt.ReadAt(data, r.off)
	r.off += int64(n)
	return
}

func print(x, y int, s string, attrs uint8) {
	for _, r := range []rune(s) {
		ui.SetCell(x, y, r, attrs)
		x++
	}
}

func printFoot() {
	w, h := ui.Size()
	for x := 0; x < w; x++ {
		ui.SetCell(x, h-2, ' ', console.AttrReverse|console.AttrBold)
	}
	filename := filename
	if filename == "" {
		filename = "[No Name]"
	}
	if textBuf.Modified() {
		filename += " [+]"
	}
	print(0, h-2, filename, console.AttrReverse|console.AttrBold)
}
<|endoftext|>