<commit_before>// +build linux
// +build cgo

package idmap

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"unsafe"

	"golang.org/x/sys/unix"

	// Used by cgo
	_ "github.com/lxc/lxd/lxd/include"

	"github.com/lxc/lxd/shared"
)

// #cgo LDFLAGS: -lacl
/*
#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif
#include <byteswap.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/capability.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/acl.h>

// Needs to be included at the end
#include <sys/xattr.h>

#include "../../lxd/include/memory_utils.h"

#ifndef VFS_CAP_REVISION_1
#define VFS_CAP_REVISION_1 0x01000000
#endif

#ifndef VFS_CAP_REVISION_2
#define VFS_CAP_REVISION_2 0x02000000
#endif

#ifndef VFS_CAP_REVISION_3
#define VFS_CAP_REVISION_3 0x03000000
struct vfs_ns_cap_data {
	__le32 magic_etc;
	struct {
		__le32 permitted;
		__le32 inheritable;
	} data[VFS_CAP_U32];
	__le32 rootid;
};
#endif

#if __BYTE_ORDER == __BIG_ENDIAN
#define BE32_TO_LE32(x) bswap_32(x)
#else
#define BE32_TO_LE32(x) (x)
#endif

int set_vfs_ns_caps(char *path, char *caps, ssize_t len, uint32_t uid)
{
	// Works because vfs_ns_cap_data is a superset of vfs_cap_data (rootid
	// field added to the end)
	struct vfs_ns_cap_data ns_xattr;

	memset(&ns_xattr, 0, sizeof(ns_xattr));
	memcpy(&ns_xattr, caps, len);
	ns_xattr.magic_etc &= ~(VFS_CAP_REVISION_1 | VFS_CAP_REVISION_2);
	ns_xattr.magic_etc |= VFS_CAP_REVISION_3;
	ns_xattr.rootid = BE32_TO_LE32(uid);

	return setxattr(path, "security.capability", &ns_xattr, sizeof(ns_xattr), 0);
}

int set_dummy_fs_ns_caps(const char *path)
{
	#define __raise_cap_permitted(x, ns_cap_data)   ns_cap_data.data[(x)>>5].permitted   |= (1<<((x)&31))

	struct vfs_ns_cap_data ns_xattr;

	memset(&ns_xattr, 0, sizeof(ns_xattr));
        __raise_cap_permitted(CAP_NET_RAW, ns_xattr);
	ns_xattr.magic_etc |= VFS_CAP_REVISION_3 | VFS_CAP_FLAGS_EFFECTIVE;
	ns_xattr.rootid = BE32_TO_LE32(1000000);

	return setxattr(path, "security.capability", &ns_xattr, sizeof(ns_xattr), 0);
}

int shiftowner(char *basepath, char *path, int uid, int gid)
{
	__do_close int fd = -EBADF;
	int ret;
	char fdpath[PATH_MAX], realpath[PATH_MAX];
	struct stat sb;

	fd = open(path, O_PATH | O_NOFOLLOW);
	if (fd < 0) {
		perror("Failed open");
		return 1;
	}

	ret = sprintf(fdpath, "/proc/self/fd/%d", fd);
	if (ret < 0) {
		perror("Failed sprintf");
		return 1;
	}

	ret = readlink(fdpath, realpath, PATH_MAX);
	if (ret < 0) {
		perror("Failed readlink");
		return 1;
	}

	if (strlen(realpath) < strlen(basepath)) {
		printf("Invalid path, source (%s) is outside of basepath (%s)\n", realpath, basepath);
		return 1;
	}

	if (strncmp(realpath, basepath, strlen(basepath))) {
		printf("Invalid path, source (%s) is outside of basepath " "(%s).\n", realpath, basepath);
		return 1;
	}

	ret = fstat(fd, &sb);
	if (ret < 0) {
		perror("Failed fstat");
		return 1;
	}

	ret = fchownat(fd, "", uid, gid, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
	if (ret < 0) {
		perror("Failed chown");
		return 1;
	}

	if (!S_ISLNK(sb.st_mode)) {
		ret = chmod(fdpath, sb.st_mode);
		if (ret < 0) {
			perror("Failed chmod");
			return 1;
		}
	}

	return 0;
}
*/
import "C"

// ShiftOwner updates uid and gid for a file when entering/exiting a namespace
func ShiftOwner(basepath string, path string, uid int, gid int) error {
	cbasepath := C.CString(basepath)
	defer C.free(unsafe.Pointer(cbasepath))

	cpath := C.CString(path)
	defer C.free(unsafe.Pointer(cpath))

	r := C.shiftowner(cbasepath, cpath, C.int(uid), C.int(gid))
	if r != 0 {
		return fmt.Errorf("Failed to change ownership of: %s", path)
	}

	return nil
}

// GetCaps extracts the list of capabilities effective on the file
func GetCaps(path string) ([]byte, error) {
	xattrs, err := shared.GetAllXattr(path)
	if err != nil {
		return nil, err
	}

	valueStr, ok := xattrs["security.capability"]
	if !ok {
		return nil, nil
	}

	return []byte(valueStr), nil
}

// SetCaps applies the caps for a particular root uid
func SetCaps(path string, caps []byte, uid int64) error {
	cpath := C.CString(path)
	defer C.free(unsafe.Pointer(cpath))

	ccaps := C.CString(string(caps))
	defer C.free(unsafe.Pointer(ccaps))

	r := C.set_vfs_ns_caps(cpath, ccaps, C.ssize_t(len(caps)), C.uint32_t(uid))
	if r != 0 {
		return fmt.Errorf("Failed to apply capabilities to: %s", path)
	}

	return nil
}

// ShiftACL updates uid and gid for file ACLs when entering/exiting a namespace
func ShiftACL(path string, shiftIds func(uid int64, gid int64) (int64, int64)) error {
	err := shiftAclType(path, C.ACL_TYPE_ACCESS, shiftIds)
	if err != nil {
		return err
	}

	err = shiftAclType(path, C.ACL_TYPE_DEFAULT, shiftIds)
	if err != nil {
		return err
	}

	return nil
}

func shiftAclType(path string, aclType int, shiftIds func(uid int64, gid int64) (int64, int64)) error {
	// Convert the path to something usable with cgo
	cpath := C.CString(path)
	defer C.free(unsafe.Pointer(cpath))

	// Read the current ACL set for the requested type
	acl := C.acl_get_file(cpath, C.uint(aclType))
	if acl == nil {
		return nil
	}
	defer C.acl_free(unsafe.Pointer(acl))

	// Iterate through all ACL entries
	update := false
	for entryId := C.ACL_FIRST_ENTRY; ; entryId = C.ACL_NEXT_ENTRY {
		var ent C.acl_entry_t
		var tag C.acl_tag_t

		// Get the ACL entry
		ret := C.acl_get_entry(acl, C.int(entryId), &ent)
		if ret == 0 {
			break
		} else if ret < 0 {
			return fmt.Errorf("Failed to get the ACL entry for %s", path)
		}

		// Get the ACL type
		ret = C.acl_get_tag_type(ent, &tag)
		if ret == -1 {
			return fmt.Errorf("Failed to get the ACL type for %s", path)
		}

		// We only care about user and group ACLs, copy anything else
		if tag != C.ACL_USER && tag != C.ACL_GROUP {
			continue
		}

		// Get the value
		idp := (*C.id_t)(C.acl_get_qualifier(ent))
		if idp == nil {
			return fmt.Errorf("Failed to get current ACL value for %s", path)
		}

		// Shift the value
		newId, _ := shiftIds((int64)(*idp), -1)

		// Update the new entry with the shifted value
		ret = C.acl_set_qualifier(ent, unsafe.Pointer(&newId))
		if ret == -1 {
			return fmt.Errorf("Failed to set ACL qualifier on %s", path)
		}

		update = true
	}

	// Update the on-disk ACLs to match
	if update {
		ret := C.acl_set_file(cpath, C.uint(aclType), acl)
		if ret == -1 {
			return fmt.Errorf("Failed to change ACLs on %s", path)
		}
	}

	return nil
}

func SupportsVFS3Fscaps(prefix string) bool {
	tmpfile, err := ioutil.TempFile(prefix, ".lxd_fcaps_v3_")
	if err != nil {
		return false
	}
	tmpfile.Close()
	defer os.Remove(tmpfile.Name())

	err = os.Chmod(tmpfile.Name(), 0001)
	if err != nil {
		return false
	}

	cpath := C.CString(tmpfile.Name())
	defer C.free(unsafe.Pointer(cpath))

	r := C.set_dummy_fs_ns_caps(cpath)
	if r != 0 {
		return false
	}

	cmd := exec.Command(tmpfile.Name())
	err = cmd.Run()
	if err != nil {
		errno, isErrno := shared.GetErrno(err)
		if isErrno && (errno == unix.ERANGE || errno == unix.EOVERFLOW) {
			return false
		}

		return true
	}

	return true
}
<commit_msg>shift_linux: tweak ACL handling<commit_after>// +build linux
// +build cgo

package idmap

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"unsafe"

	"golang.org/x/sys/unix"

	// Used by cgo
	_ "github.com/lxc/lxd/lxd/include"

	"github.com/lxc/lxd/shared"
)

// #cgo LDFLAGS: -lacl
/*
#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif
#include <byteswap.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/capability.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/acl.h>

// Needs to be included at the end
#include <sys/xattr.h>

#include "../../lxd/include/memory_utils.h"

#ifndef VFS_CAP_REVISION_1
#define VFS_CAP_REVISION_1 0x01000000
#endif

#ifndef VFS_CAP_REVISION_2
#define VFS_CAP_REVISION_2 0x02000000
#endif

#ifndef VFS_CAP_REVISION_3
#define VFS_CAP_REVISION_3 0x03000000
struct vfs_ns_cap_data {
	__le32 magic_etc;
	struct {
		__le32 permitted;
		__le32 inheritable;
	} data[VFS_CAP_U32];
	__le32 rootid;
};
#endif

#if __BYTE_ORDER == __BIG_ENDIAN
#define BE32_TO_LE32(x) bswap_32(x)
#else
#define BE32_TO_LE32(x) (x)
#endif

int set_vfs_ns_caps(char *path, char *caps, ssize_t len, uint32_t uid)
{
	// Works because vfs_ns_cap_data is a superset of vfs_cap_data (rootid
	// field added to the end)
	struct vfs_ns_cap_data ns_xattr;

	memset(&ns_xattr, 0, sizeof(ns_xattr));
	memcpy(&ns_xattr, caps, len);
	ns_xattr.magic_etc &= ~(VFS_CAP_REVISION_1 | VFS_CAP_REVISION_2);
	ns_xattr.magic_etc |= VFS_CAP_REVISION_3;
	ns_xattr.rootid = BE32_TO_LE32(uid);

	return setxattr(path, "security.capability", &ns_xattr, sizeof(ns_xattr), 0);
}

int set_dummy_fs_ns_caps(const char *path)
{
	#define __raise_cap_permitted(x, ns_cap_data)   ns_cap_data.data[(x)>>5].permitted   |= (1<<((x)&31))

	struct vfs_ns_cap_data ns_xattr;

	memset(&ns_xattr, 0, sizeof(ns_xattr));
        __raise_cap_permitted(CAP_NET_RAW, ns_xattr);
	ns_xattr.magic_etc |= VFS_CAP_REVISION_3 | VFS_CAP_FLAGS_EFFECTIVE;
	ns_xattr.rootid = BE32_TO_LE32(1000000);

	return setxattr(path, "security.capability", &ns_xattr, sizeof(ns_xattr), 0);
}

int shiftowner(char *basepath, char *path, int uid, int gid)
{
	__do_close int fd = -EBADF;
	int ret;
	char fdpath[PATH_MAX], realpath[PATH_MAX];
	struct stat sb;

	fd = open(path, O_PATH | O_NOFOLLOW);
	if (fd < 0) {
		perror("Failed open");
		return 1;
	}

	ret = sprintf(fdpath, "/proc/self/fd/%d", fd);
	if (ret < 0) {
		perror("Failed sprintf");
		return 1;
	}

	ret = readlink(fdpath, realpath, PATH_MAX);
	if (ret < 0) {
		perror("Failed readlink");
		return 1;
	}

	if (strlen(realpath) < strlen(basepath)) {
		printf("Invalid path, source (%s) is outside of basepath (%s)\n", realpath, basepath);
		return 1;
	}

	if (strncmp(realpath, basepath, strlen(basepath))) {
		printf("Invalid path, source (%s) is outside of basepath " "(%s).\n", realpath, basepath);
		return 1;
	}

	ret = fstat(fd, &sb);
	if (ret < 0) {
		perror("Failed fstat");
		return 1;
	}

	ret = fchownat(fd, "", uid, gid, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
	if (ret < 0) {
		perror("Failed chown");
		return 1;
	}

	if (!S_ISLNK(sb.st_mode)) {
		ret = chmod(fdpath, sb.st_mode);
		if (ret < 0) {
			perror("Failed chmod");
			return 1;
		}
	}

	return 0;
}
*/
import "C"

// ShiftOwner updates uid and gid for a file when entering/exiting a namespace
func ShiftOwner(basepath string, path string, uid int, gid int) error {
	cbasepath := C.CString(basepath)
	defer C.free(unsafe.Pointer(cbasepath))

	cpath := C.CString(path)
	defer C.free(unsafe.Pointer(cpath))

	r := C.shiftowner(cbasepath, cpath, C.int(uid), C.int(gid))
	if r != 0 {
		return fmt.Errorf("Failed to change ownership of: %s", path)
	}

	return nil
}

// GetCaps extracts the list of capabilities effective on the file
func GetCaps(path string) ([]byte, error) {
	xattrs, err := shared.GetAllXattr(path)
	if err != nil {
		return nil, err
	}

	valueStr, ok := xattrs["security.capability"]
	if !ok {
		return nil, nil
	}

	return []byte(valueStr), nil
}

// SetCaps applies the caps for a particular root uid
func SetCaps(path string, caps []byte, uid int64) error {
	cpath := C.CString(path)
	defer C.free(unsafe.Pointer(cpath))

	ccaps := C.CString(string(caps))
	defer C.free(unsafe.Pointer(ccaps))

	r := C.set_vfs_ns_caps(cpath, ccaps, C.ssize_t(len(caps)), C.uint32_t(uid))
	if r != 0 {
		return fmt.Errorf("Failed to apply capabilities to: %s", path)
	}

	return nil
}

// ShiftACL updates uid and gid for file ACLs when entering/exiting a namespace
func ShiftACL(path string, shiftIds func(uid int64, gid int64) (int64, int64)) error {
	err := shiftAclType(path, C.ACL_TYPE_ACCESS, shiftIds)
	if err != nil {
		return err
	}

	err = shiftAclType(path, C.ACL_TYPE_DEFAULT, shiftIds)
	if err != nil {
		return err
	}

	return nil
}

func shiftAclType(path string, aclType int, shiftIds func(uid int64, gid int64) (int64, int64)) error {
	// Convert the path to something usable with cgo
	cpath := C.CString(path)
	defer C.free(unsafe.Pointer(cpath))

	// Read the current ACL set for the requested type
	acl := C.acl_get_file(cpath, C.uint(aclType))
	if acl == nil {
		return nil
	}
	defer C.acl_free(unsafe.Pointer(acl))

	// Iterate through all ACL entries
	update := false
	for entryId := C.ACL_FIRST_ENTRY; ; entryId = C.ACL_NEXT_ENTRY {
		var ent C.acl_entry_t
		var tag C.acl_tag_t

		// Get the ACL entry
		ret := C.acl_get_entry(acl, C.int(entryId), &ent)
		if ret == 0 {
			break
		} else if ret < 0 {
			return fmt.Errorf("Failed to get the ACL entry for %s", path)
		}

		// Get the ACL type
		ret = C.acl_get_tag_type(ent, &tag)
		if ret == -1 {
			return fmt.Errorf("Failed to get the ACL type for %s", path)
		}

		// We only care about user and group ACLs, copy anything else
		if tag != C.ACL_USER && tag != C.ACL_GROUP {
			continue
		}

		// Get the value
		idp := (*C.id_t)(C.acl_get_qualifier(ent))
		if idp == nil {
			return fmt.Errorf("Failed to get current ACL value for %s", path)
		}

		// Shift the value
		newId := int64(-1)
		if tag == C.ACL_USER {
			newId, _ = shiftIds((int64)(*idp), -1)
		} else {
			_, newId = shiftIds(-1, (int64)(*idp))
		}

		// Update the new entry with the shifted value
		ret = C.acl_set_qualifier(ent, unsafe.Pointer(&newId))
		if ret == -1 {
			return fmt.Errorf("Failed to set ACL qualifier on %s", path)
		}

		update = true
	}

	// Update the on-disk ACLs to match
	if update {
		ret, err := C.acl_set_file(cpath, C.uint(aclType), acl)
		if ret < 0 {
			return fmt.Errorf("%s - Failed to change ACLs on %s", err, path)
		}
	}

	return nil
}

func SupportsVFS3Fscaps(prefix string) bool {
	tmpfile, err := ioutil.TempFile(prefix, ".lxd_fcaps_v3_")
	if err != nil {
		return false
	}
	tmpfile.Close()
	defer os.Remove(tmpfile.Name())

	err = os.Chmod(tmpfile.Name(), 0001)
	if err != nil {
		return false
	}

	cpath := C.CString(tmpfile.Name())
	defer C.free(unsafe.Pointer(cpath))

	r := C.set_dummy_fs_ns_caps(cpath)
	if r != 0 {
		return false
	}

	cmd := exec.Command(tmpfile.Name())
	err = cmd.Run()
	if err != nil {
		errno, isErrno := shared.GetErrno(err)
		if isErrno && (errno == unix.ERANGE || errno == unix.EOVERFLOW) {
			return false
		}

		return true
	}

	return true
}
<|endoftext|>


<commit_before>// Copyright 2013 sigu-399 ( https://github.com/sigu-399 )
//
// 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.

// author  			sigu-399
// author-github 	https://github.com/sigu-399
// author-mail		sigu.399@gmail.com
//
// repository-name	gojsonpointer
// repository-desc	An implementation of JSON Pointer - Go language
//
// description		Main and unique file.
//
// created      	25-02-2013

package gojsonpointer

import (
	"errors"
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

const (
	const_empty_pointer     = ``
	const_pointer_separator = `/`

	const_invalid_start = `JSON pointer must be empty or start with a "` + const_pointer_separator
)

type implStruct struct {
	mode string // "SET" or "GET"

	inDocument interface{}

	setInValue interface{}

	getOutNode interface{}
	getOutKind reflect.Kind
	outError   error
}

func NewJsonPointer(jsonPointerString string) (JsonPointer, error) {

	var p JsonPointer
	err := p.parse(jsonPointerString)
	return p, err

}

type JsonPointer struct {
	referenceTokens []string
}

// "Constructor", parses the given string JSON pointer
func (p *JsonPointer) parse(jsonPointerString string) error {

	var err error

	if jsonPointerString != const_empty_pointer {
		if !strings.HasPrefix(jsonPointerString, const_pointer_separator) {
			err = errors.New(const_invalid_start)
		} else {
			referenceTokens := strings.Split(jsonPointerString, const_pointer_separator)
			for _, referenceToken := range referenceTokens[1:] {
				p.referenceTokens = append(p.referenceTokens, referenceToken)
			}
		}
	}

	return err
}

// Uses the pointer to retrieve a value from a JSON document
func (p *JsonPointer) Get(document interface{}) (interface{}, reflect.Kind, error) {

	is := &implStruct{mode: "GET", inDocument: document}
	p.implementation(is)
	return is.getOutNode, is.getOutKind, is.outError

}

// Uses the pointer to update a value from a JSON document
func (p *JsonPointer) Set(document interface{}, value interface{}) (interface{}, error) {

	is := &implStruct{mode: "SET", inDocument: document, setInValue: value}
	p.implementation(is)
	return document, is.outError

}

// Both Get and Set functions use the same implementation to avoid code duplication
func (p *JsonPointer) implementation(i *implStruct) {

	kind := reflect.Invalid

	// Full document when empty
	if len(p.referenceTokens) == 0 {
		i.getOutNode = i.inDocument
		i.outError = nil
		i.getOutKind = kind
		i.outError = nil
		return
	}

	node := i.inDocument

	for ti, token := range p.referenceTokens {

		decodedToken := decodeReferenceToken(token)
		isLastToken := ti == len(p.referenceTokens)-1

		rValue := reflect.ValueOf(node)
		kind = rValue.Kind()

		switch kind {

		case reflect.Map:
			m := node.(map[string]interface{})
			if _, ok := m[decodedToken]; ok {
				node = m[decodedToken]
				if isLastToken && i.mode == "SET" {
					m[decodedToken] = i.setInValue
				}
			} else {
				i.outError = errors.New(fmt.Sprintf("Object has no key '%s'", token))
				i.getOutKind = kind
				i.getOutNode = nil
				return
			}

		case reflect.Slice:
			s := node.([]interface{})
			tokenIndex, err := strconv.Atoi(token)
			if err != nil {
				i.outError = errors.New(fmt.Sprintf("Invalid array index '%s'", token))
				i.getOutKind = kind
				i.getOutNode = nil
				return
			}
			sLength := len(s)
			if tokenIndex < 0 || tokenIndex >= sLength {
				i.outError = errors.New(fmt.Sprintf("Out of bound array[0,%d] index '%d'", tokenIndex, sLength))
				i.getOutKind = kind
				i.getOutNode = nil
				return
			}

			node = s[tokenIndex]
			if isLastToken && i.mode == "SET" {
				s[tokenIndex] = i.setInValue
			}

		default:
			i.outError = errors.New(fmt.Sprintf("Invalid token reference '%s'", token))
			i.getOutKind = kind
			i.getOutNode = nil
			return
		}

	}

	rValue := reflect.ValueOf(node)
	kind = rValue.Kind()

	i.getOutNode = node
	i.getOutKind = kind
	i.outError = nil
}

// Pointer to string representation function
func (p *JsonPointer) String() string {

	if len(p.referenceTokens) == 0 {
		return const_empty_pointer
	}

	pointerString := const_pointer_separator + strings.Join(p.referenceTokens, const_pointer_separator)

	return pointerString
}

// Specific JSON pointer encoding here
// ~0 => ~
// ~1 => /
// ... and vice versa

const (
	const_encoded_reference_token_0 = `~0`
	const_encoded_reference_token_1 = `~1`
	const_decoded_reference_token_0 = `~`
	const_decoded_reference_token_1 = `/`
)

func decodeReferenceToken(token string) string {
	step1 := strings.Replace(token, const_encoded_reference_token_1, const_decoded_reference_token_1, -1)
	step2 := strings.Replace(step1, const_encoded_reference_token_0, const_decoded_reference_token_0, -1)
	return step2
}

func encodeReferenceToken(token string) string {
	step1 := strings.Replace(token, const_decoded_reference_token_1, const_encoded_reference_token_1, -1)
	step2 := strings.Replace(step1, const_decoded_reference_token_0, const_encoded_reference_token_0, -1)
	return step2
}
<commit_msg>fixed array out of bound error message<commit_after>// Copyright 2013 sigu-399 ( https://github.com/sigu-399 )
//
// 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.

// author  			sigu-399
// author-github 	https://github.com/sigu-399
// author-mail		sigu.399@gmail.com
//
// repository-name	gojsonpointer
// repository-desc	An implementation of JSON Pointer - Go language
//
// description		Main and unique file.
//
// created      	25-02-2013

package gojsonpointer

import (
	"errors"
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

const (
	const_empty_pointer     = ``
	const_pointer_separator = `/`

	const_invalid_start = `JSON pointer must be empty or start with a "` + const_pointer_separator
)

type implStruct struct {
	mode string // "SET" or "GET"

	inDocument interface{}

	setInValue interface{}

	getOutNode interface{}
	getOutKind reflect.Kind
	outError   error
}

func NewJsonPointer(jsonPointerString string) (JsonPointer, error) {

	var p JsonPointer
	err := p.parse(jsonPointerString)
	return p, err

}

type JsonPointer struct {
	referenceTokens []string
}

// "Constructor", parses the given string JSON pointer
func (p *JsonPointer) parse(jsonPointerString string) error {

	var err error

	if jsonPointerString != const_empty_pointer {
		if !strings.HasPrefix(jsonPointerString, const_pointer_separator) {
			err = errors.New(const_invalid_start)
		} else {
			referenceTokens := strings.Split(jsonPointerString, const_pointer_separator)
			for _, referenceToken := range referenceTokens[1:] {
				p.referenceTokens = append(p.referenceTokens, referenceToken)
			}
		}
	}

	return err
}

// Uses the pointer to retrieve a value from a JSON document
func (p *JsonPointer) Get(document interface{}) (interface{}, reflect.Kind, error) {

	is := &implStruct{mode: "GET", inDocument: document}
	p.implementation(is)
	return is.getOutNode, is.getOutKind, is.outError

}

// Uses the pointer to update a value from a JSON document
func (p *JsonPointer) Set(document interface{}, value interface{}) (interface{}, error) {

	is := &implStruct{mode: "SET", inDocument: document, setInValue: value}
	p.implementation(is)
	return document, is.outError

}

// Both Get and Set functions use the same implementation to avoid code duplication
func (p *JsonPointer) implementation(i *implStruct) {

	kind := reflect.Invalid

	// Full document when empty
	if len(p.referenceTokens) == 0 {
		i.getOutNode = i.inDocument
		i.outError = nil
		i.getOutKind = kind
		i.outError = nil
		return
	}

	node := i.inDocument

	for ti, token := range p.referenceTokens {

		decodedToken := decodeReferenceToken(token)
		isLastToken := ti == len(p.referenceTokens)-1

		rValue := reflect.ValueOf(node)
		kind = rValue.Kind()

		switch kind {

		case reflect.Map:
			m := node.(map[string]interface{})
			if _, ok := m[decodedToken]; ok {
				node = m[decodedToken]
				if isLastToken && i.mode == "SET" {
					m[decodedToken] = i.setInValue
				}
			} else {
				i.outError = errors.New(fmt.Sprintf("Object has no key '%s'", token))
				i.getOutKind = kind
				i.getOutNode = nil
				return
			}

		case reflect.Slice:
			s := node.([]interface{})
			tokenIndex, err := strconv.Atoi(token)
			if err != nil {
				i.outError = errors.New(fmt.Sprintf("Invalid array index '%s'", token))
				i.getOutKind = kind
				i.getOutNode = nil
				return
			}
			sLength := len(s)
			if tokenIndex < 0 || tokenIndex >= sLength {
				i.outError = errors.New(fmt.Sprintf("Out of bound array[0,%d] index '%d'", sLength, tokenIndex))
				i.getOutKind = kind
				i.getOutNode = nil
				return
			}

			node = s[tokenIndex]
			if isLastToken && i.mode == "SET" {
				s[tokenIndex] = i.setInValue
			}

		default:
			i.outError = errors.New(fmt.Sprintf("Invalid token reference '%s'", token))
			i.getOutKind = kind
			i.getOutNode = nil
			return
		}

	}

	rValue := reflect.ValueOf(node)
	kind = rValue.Kind()

	i.getOutNode = node
	i.getOutKind = kind
	i.outError = nil
}

// Pointer to string representation function
func (p *JsonPointer) String() string {

	if len(p.referenceTokens) == 0 {
		return const_empty_pointer
	}

	pointerString := const_pointer_separator + strings.Join(p.referenceTokens, const_pointer_separator)

	return pointerString
}

// Specific JSON pointer encoding here
// ~0 => ~
// ~1 => /
// ... and vice versa

const (
	const_encoded_reference_token_0 = `~0`
	const_encoded_reference_token_1 = `~1`
	const_decoded_reference_token_0 = `~`
	const_decoded_reference_token_1 = `/`
)

func decodeReferenceToken(token string) string {
	step1 := strings.Replace(token, const_encoded_reference_token_1, const_decoded_reference_token_1, -1)
	step2 := strings.Replace(step1, const_encoded_reference_token_0, const_decoded_reference_token_0, -1)
	return step2
}

func encodeReferenceToken(token string) string {
	step1 := strings.Replace(token, const_decoded_reference_token_1, const_encoded_reference_token_1, -1)
	step2 := strings.Replace(step1, const_decoded_reference_token_0, const_encoded_reference_token_0, -1)
	return step2
}
<|endoftext|>


<commit_before><commit_msg>Adding doc.go to util package.<commit_after><|endoftext|>


<commit_before>package node

import (
	"regexp"
	"time"

	"github.com/MG-RAST/Shock/shock-server/cache"
	"github.com/MG-RAST/Shock/shock-server/conf"
	"github.com/MG-RAST/Shock/shock-server/logger"
	"github.com/MG-RAST/Shock/shock-server/node/locker"
	"gopkg.in/mgo.v2/bson"
)

var (
	Ttl         *NodeReaper
	ExpireRegex = regexp.MustCompile(`^(\d+)(M|H|D)$`)
)

func InitReaper() {
	Ttl = NewNodeReaper()
}

type NodeReaper struct{}

func NewNodeReaper() *NodeReaper {
	return &NodeReaper{}
}

func (nr *NodeReaper) Handle() {
	waitDuration := time.Duration(conf.EXPIRE_WAIT) * time.Minute
	for {

		// sleep
		time.Sleep(waitDuration)
		// query to get expired nodes
		nodes := Nodes{}
		query := nr.getQuery()
		nodes.GetAll(query)
		// delete expired nodes
		for _, n := range nodes {
			logger.Infof("Deleting expired node: %s", n.Id)
			if err := n.Delete(); err != nil {
				err_msg := "err:@node_delete: " + err.Error()
				logger.Error(err_msg)
			}
		}
		// garbage collection: remove old nodes from Lockers, value is hours old
		locker.NodeLockMgr.RemoveOld(1)
		locker.FileLockMgr.RemoveOld(6)
		locker.IndexLockMgr.RemoveOld(6)

		// we do not start deletings files if we are not in cache mode
		if conf.PATH_CACHE == "" {
			continue
		}
	Loop2:
		// start a FILE REAPER that loops thru CacheMap[*]
		for ID := range cache.CacheMap {

			//fmt.Printf("(Reaper-->FileReaper) checking %s in cache\n", ID)

			now := time.Now()
			lru := cache.CacheMap[ID].Access

			diff := now.Sub(lru)

			// we use a very simple scheme for caching initially (file not used for 1 day)
			if diff.Hours() < float64(conf.CACHE_TTL) {
				//	fmt.Printf("(Reaper-->FileReaper) not deleting %s from cache it was last accessed %s hours ago\n", ID, diff.Hours())
				continue
			}

			// START HERE ON MONDAY
			// ideally we would only have things in the cache that have a remote location
			// maybe init cache from Mongo instead of local filesystem? it'd be faster...

			//fmt.Printf("(Reaper-->FileReaper) trying to delete %s from cache\n ", ID)

			n, _ := Load(ID)
			for _, loc := range n.Locations {
				// delete only if other locations exist
				locObj, ok := conf.LocationsMap[loc]
				if !ok {
					logger.Infof("(Reaper-->FileReaper) location %s is not OK \n ", loc)
					continue
				}
				//fmt.Printf("(Reaper-->FileReaper) locObj.Persistent =  %b  \n ", locObj.Persistent)

				if locObj.Persistent == true {
					logger.Infof("(Reaper-->FileReaper) has remote Location (%s) removing from Cache: %s", loc, ID)

					cache.Remove(ID)
					break Loop2 // the innermost loop
				}
			}

			logger.Infof("(Reaper-->FileReaper) cannot delete %s from cache", ID)

		}
	}

	return
}

func (nr *NodeReaper) getQuery() (query bson.M) {
	hasExpire := bson.M{"expiration": bson.M{"$exists": true}}   // has the field
	toExpire := bson.M{"expiration": bson.M{"$ne": time.Time{}}} // value has been set, not default
	isExpired := bson.M{"expiration": bson.M{"$lt": time.Now()}} // value is too old
	query = bson.M{"$and": []bson.M{hasExpire, toExpire, isExpired}}
	return
}
<commit_msg>fixed main loop in expire to handle more than one cache item throw error when encountering location not configured for server<commit_after>package node

import (
	"regexp"
	"time"

	"github.com/MG-RAST/Shock/shock-server/cache"
	"github.com/MG-RAST/Shock/shock-server/conf"
	"github.com/MG-RAST/Shock/shock-server/logger"
	"github.com/MG-RAST/Shock/shock-server/node/locker"
	"gopkg.in/mgo.v2/bson"
)

var (
	Ttl         *NodeReaper
	ExpireRegex = regexp.MustCompile(`^(\d+)(M|H|D)$`)
)

func InitReaper() {
	Ttl = NewNodeReaper()
}

type NodeReaper struct{}

func NewNodeReaper() *NodeReaper {
	return &NodeReaper{}
}

func (nr *NodeReaper) Handle() {
	waitDuration := time.Duration(conf.EXPIRE_WAIT) * time.Minute
	for {

		// sleep
		time.Sleep(waitDuration)
		// query to get expired nodes
		nodes := Nodes{}
		query := nr.getQuery()
		nodes.GetAll(query)
		// delete expired nodes
		for _, n := range nodes {
			logger.Infof("Deleting expired node: %s", n.Id)
			if err := n.Delete(); err != nil {
				err_msg := "err:@node_delete: " + err.Error()
				logger.Error(err_msg)
			}
		}
		// garbage collection: remove old nodes from Lockers, value is hours old
		locker.NodeLockMgr.RemoveOld(1)
		locker.FileLockMgr.RemoveOld(6)
		locker.IndexLockMgr.RemoveOld(6)

		// we do not start deletings files if we are not in cache mode
		if conf.PATH_CACHE == "" {
			continue
		}
	Loop2:
		// start a FILE REAPER that loops thru CacheMap[*]
		for ID := range cache.CacheMap {

			//fmt.Printf("(Reaper-->FileReaper) checking %s in cache\n", ID)

			now := time.Now()
			lru := cache.CacheMap[ID].Access
			diff := now.Sub(lru)

			// we use a very simple scheme for caching initially (file not used for 1 day)
			if diff.Hours() < float64(conf.CACHE_TTL) {
				//	fmt.Printf("(Reaper-->FileReaper) not deleting %s from cache it was last accessed %s hours ago\n", ID, diff.Hours())
				continue
			}

			n, err := Load(ID)
			if err != nil {
				logger.Infof("(Reaper-->FileReaper) Cannot access CacheMapItem[%s] (%s)", ID, err.Error())
				continue
			}

			for _, loc := range n.Locations {
				// delete only if other locations exist
				locObj, ok := conf.LocationsMap[loc]
				if !ok {
					logger.Errorf("(Reaper-->FileReaper) location %s is not defined in this server instance \n ", loc)
					continue
				}
				//fmt.Printf("(Reaper-->FileReaper) locObj.Persistent =  %b  \n ", locObj.Persistent)
				if locObj.Persistent == true {
					logger.Infof("(Reaper-->FileReaper) has remote Location (%s) removing from Cache: %s", loc, ID)

					cache.Remove(ID)
					continue Loop2 // the innermost loop
				}
			}
			logger.Errorf("(Reaper-->FileReaper) cannot delete %s from cache [This should not happen!!]", ID)
		}
	}

	return
}

func (nr *NodeReaper) getQuery() (query bson.M) {
	hasExpire := bson.M{"expiration": bson.M{"$exists": true}}   // has the field
	toExpire := bson.M{"expiration": bson.M{"$ne": time.Time{}}} // value has been set, not default
	isExpired := bson.M{"expiration": bson.M{"$lt": time.Now()}} // value is too old
	query = bson.M{"$and": []bson.M{hasExpire, toExpire, isExpired}}
	return
}
<|endoftext|>


<commit_before>package pgtype

import (
	"database/sql/driver"
	"encoding/binary"
	"fmt"
	"math"
	"strconv"
	"strings"

	"github.com/jackc/pgio"
	errors "golang.org/x/xerrors"
)

type Polygon struct {
	P      []Vec2
	Status Status
}

func (dst *Polygon) Set(src interface{}) error {
	if src == nil {
		dst.Status = Null
		return nil
	}
	err := errors.Errorf("cannot convert %v to Polygon", src)
	var p *Polygon
	switch value := src.(type) {
	case string:
		p, err = parseString(value)
	case []Vec2:
		p = &Polygon{Status: Present, P: value}
		err = nil
	case []float64:
		p, err = parseFloat64(value)
	default:
		return err
	}
	if err != nil {
		return err
	}
	*dst = *p
	return nil
}

func parseString(src string) (*Polygon, error) {
	p := &Polygon{}
	err := p.DecodeText(nil, []byte(src))
	return p, err
}

func parseFloat64(src []float64) (*Polygon, error) {
	p := &Polygon{Status: Null}
	if len(src) == 0 {
		return p, nil
	}
	if len(src)%2 != 0 {
		return p, errors.Errorf("invalid length for polygon: %v", len(src))
	}
	p.Status = Present
	p.P = make([]Vec2, 0)
	for i := 0; i < len(src); i += 2 {
		p.P = append(p.P, Vec2{X: src[i], Y: src[i+1]})
	}
	return p, nil
}

func (dst Polygon) Get() interface{} {
	switch dst.Status {
	case Present:
		return dst
	case Null:
		return nil
	default:
		return dst.Status
	}
}

func (src *Polygon) AssignTo(dst interface{}) error {
	return errors.Errorf("cannot assign %v to %T", src, dst)
}

func (dst *Polygon) DecodeText(ci *ConnInfo, src []byte) error {
	if src == nil {
		*dst = Polygon{Status: Null}
		return nil
	}

	if len(src) < 7 {
		return errors.Errorf("invalid length for Polygon: %v", len(src))
	}

	points := make([]Vec2, 0)

	str := string(src[2:])

	for {
		end := strings.IndexByte(str, ',')
		x, err := strconv.ParseFloat(str[:end], 64)
		if err != nil {
			return err
		}

		str = str[end+1:]
		end = strings.IndexByte(str, ')')

		y, err := strconv.ParseFloat(str[:end], 64)
		if err != nil {
			return err
		}

		points = append(points, Vec2{x, y})

		if end+3 < len(str) {
			str = str[end+3:]
		} else {
			break
		}
	}

	*dst = Polygon{P: points, Status: Present}
	return nil
}

func (dst *Polygon) DecodeBinary(ci *ConnInfo, src []byte) error {
	if src == nil {
		*dst = Polygon{Status: Null}
		return nil
	}

	if len(src) < 5 {
		return errors.Errorf("invalid length for Polygon: %v", len(src))
	}

	pointCount := int(binary.BigEndian.Uint32(src))
	rp := 4

	if 4+pointCount*16 != len(src) {
		return errors.Errorf("invalid length for Polygon with %d points: %v", pointCount, len(src))
	}

	points := make([]Vec2, pointCount)
	for i := 0; i < len(points); i++ {
		x := binary.BigEndian.Uint64(src[rp:])
		rp += 8
		y := binary.BigEndian.Uint64(src[rp:])
		rp += 8
		points[i] = Vec2{math.Float64frombits(x), math.Float64frombits(y)}
	}

	*dst = Polygon{
		P:      points,
		Status: Present,
	}
	return nil
}

func (src Polygon) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
	switch src.Status {
	case Null:
		return nil, nil
	case Undefined:
		return nil, errUndefined
	}

	buf = append(buf, '(')

	for i, p := range src.P {
		if i > 0 {
			buf = append(buf, ',')
		}
		buf = append(buf, fmt.Sprintf(`(%s,%s)`,
			strconv.FormatFloat(p.X, 'f', -1, 64),
			strconv.FormatFloat(p.Y, 'f', -1, 64),
		)...)
	}

	return append(buf, ')'), nil
}

func (src Polygon) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
	switch src.Status {
	case Null:
		return nil, nil
	case Undefined:
		return nil, errUndefined
	}

	buf = pgio.AppendInt32(buf, int32(len(src.P)))

	for _, p := range src.P {
		buf = pgio.AppendUint64(buf, math.Float64bits(p.X))
		buf = pgio.AppendUint64(buf, math.Float64bits(p.Y))
	}

	return buf, nil
}

// Scan implements the database/sql Scanner interface.
func (dst *Polygon) Scan(src interface{}) error {
	if src == nil {
		*dst = Polygon{Status: Null}
		return nil
	}

	switch src := src.(type) {
	case string:
		return dst.DecodeText(nil, []byte(src))
	case []byte:
		srcCopy := make([]byte, len(src))
		copy(srcCopy, src)
		return dst.DecodeText(nil, srcCopy)
	}

	return errors.Errorf("cannot scan %T", src)
}

// Value implements the database/sql/driver Valuer interface.
func (src Polygon) Value() (driver.Value, error) {
	return EncodeValueText(src)
}
<commit_msg>Add Undefined status to invalid Polygon<commit_after>package pgtype

import (
	"database/sql/driver"
	"encoding/binary"
	"fmt"
	"math"
	"strconv"
	"strings"

	"github.com/jackc/pgio"
	errors "golang.org/x/xerrors"
)

type Polygon struct {
	P      []Vec2
	Status Status
}

func (dst *Polygon) Set(src interface{}) error {
	if src == nil {
		dst.Status = Null
		return nil
	}
	err := errors.Errorf("cannot convert %v to Polygon", src)
	var p *Polygon
	switch value := src.(type) {
	case string:
		p, err = parseString(value)
	case []Vec2:
		p = &Polygon{Status: Present, P: value}
		err = nil
	case []float64:
		p, err = parseFloat64(value)
	default:
		return err
	}
	if err != nil {
		return err
	}
	*dst = *p
	return nil
}

func parseString(src string) (*Polygon, error) {
	p := &Polygon{}
	err := p.DecodeText(nil, []byte(src))
	return p, err
}

func parseFloat64(src []float64) (*Polygon, error) {
	p := &Polygon{Status: Null}
	if len(src) == 0 {
		return p, nil
	}
	if len(src)%2 != 0 {
		p.Status = Undefined
		return p, errors.Errorf("invalid length for polygon: %v", len(src))
	}
	p.Status = Present
	p.P = make([]Vec2, 0)
	for i := 0; i < len(src); i += 2 {
		p.P = append(p.P, Vec2{X: src[i], Y: src[i+1]})
	}
	return p, nil
}

func (dst Polygon) Get() interface{} {
	switch dst.Status {
	case Present:
		return dst
	case Null:
		return nil
	default:
		return dst.Status
	}
}

func (src *Polygon) AssignTo(dst interface{}) error {
	return errors.Errorf("cannot assign %v to %T", src, dst)
}

func (dst *Polygon) DecodeText(ci *ConnInfo, src []byte) error {
	if src == nil {
		*dst = Polygon{Status: Null}
		return nil
	}

	if len(src) < 7 {
		return errors.Errorf("invalid length for Polygon: %v", len(src))
	}

	points := make([]Vec2, 0)

	str := string(src[2:])

	for {
		end := strings.IndexByte(str, ',')
		x, err := strconv.ParseFloat(str[:end], 64)
		if err != nil {
			return err
		}

		str = str[end+1:]
		end = strings.IndexByte(str, ')')

		y, err := strconv.ParseFloat(str[:end], 64)
		if err != nil {
			return err
		}

		points = append(points, Vec2{x, y})

		if end+3 < len(str) {
			str = str[end+3:]
		} else {
			break
		}
	}

	*dst = Polygon{P: points, Status: Present}
	return nil
}

func (dst *Polygon) DecodeBinary(ci *ConnInfo, src []byte) error {
	if src == nil {
		*dst = Polygon{Status: Null}
		return nil
	}

	if len(src) < 5 {
		return errors.Errorf("invalid length for Polygon: %v", len(src))
	}

	pointCount := int(binary.BigEndian.Uint32(src))
	rp := 4

	if 4+pointCount*16 != len(src) {
		return errors.Errorf("invalid length for Polygon with %d points: %v", pointCount, len(src))
	}

	points := make([]Vec2, pointCount)
	for i := 0; i < len(points); i++ {
		x := binary.BigEndian.Uint64(src[rp:])
		rp += 8
		y := binary.BigEndian.Uint64(src[rp:])
		rp += 8
		points[i] = Vec2{math.Float64frombits(x), math.Float64frombits(y)}
	}

	*dst = Polygon{
		P:      points,
		Status: Present,
	}
	return nil
}

func (src Polygon) EncodeText(ci *ConnInfo, buf []byte) ([]byte, error) {
	switch src.Status {
	case Null:
		return nil, nil
	case Undefined:
		return nil, errUndefined
	}

	buf = append(buf, '(')

	for i, p := range src.P {
		if i > 0 {
			buf = append(buf, ',')
		}
		buf = append(buf, fmt.Sprintf(`(%s,%s)`,
			strconv.FormatFloat(p.X, 'f', -1, 64),
			strconv.FormatFloat(p.Y, 'f', -1, 64),
		)...)
	}

	return append(buf, ')'), nil
}

func (src Polygon) EncodeBinary(ci *ConnInfo, buf []byte) ([]byte, error) {
	switch src.Status {
	case Null:
		return nil, nil
	case Undefined:
		return nil, errUndefined
	}

	buf = pgio.AppendInt32(buf, int32(len(src.P)))

	for _, p := range src.P {
		buf = pgio.AppendUint64(buf, math.Float64bits(p.X))
		buf = pgio.AppendUint64(buf, math.Float64bits(p.Y))
	}

	return buf, nil
}

// Scan implements the database/sql Scanner interface.
func (dst *Polygon) Scan(src interface{}) error {
	if src == nil {
		*dst = Polygon{Status: Null}
		return nil
	}

	switch src := src.(type) {
	case string:
		return dst.DecodeText(nil, []byte(src))
	case []byte:
		srcCopy := make([]byte, len(src))
		copy(srcCopy, src)
		return dst.DecodeText(nil, srcCopy)
	}

	return errors.Errorf("cannot scan %T", src)
}

// Value implements the database/sql/driver Valuer interface.
func (src Polygon) Value() (driver.Value, error) {
	return EncodeValueText(src)
}
<|endoftext|>


<commit_before>package ui

import (
	"context"
	"encoding/base64"
	"fmt"
	"hash/fnv"
	"net"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/temoto/alive"
	"github.com/temoto/vender/currency"
	"github.com/temoto/vender/engine"
	"github.com/temoto/vender/engine/inventory"
	"github.com/temoto/vender/hardware/input"
	"github.com/temoto/vender/head/money"
	tele_api "github.com/temoto/vender/head/tele/api"
	"github.com/temoto/vender/helpers"
	"github.com/temoto/vender/state"
)

const (
	serviceMenuInventory = "inventory"
	serviceMenuTest      = "test"
	serviceMenuReboot    = "reboot"
	serviceMenuNetwork   = "network"
	serviceMenuReport    = "report"
)

var /*const*/ serviceMenu = []string{
	serviceMenuInventory,
	serviceMenuTest,
	serviceMenuReboot,
	serviceMenuNetwork,
	serviceMenuReport,
}
var /*const*/ serviceMenuMax = uint8(len(serviceMenu) - 1)

type uiService struct {
	// config
	resetTimeout time.Duration
	SecretSalt   []byte

	// state
	askReport bool
	menuIdx   uint8
	invIdx    uint8
	invList   []*inventory.Stock
	testIdx   uint8
	testList  []engine.Doer
}

func (self *uiService) Init(ctx context.Context) {
	g := state.GetGlobal(ctx)
	config := g.Config.UI.Service
	self.SecretSalt = []byte{0} // FIXME read from config
	self.resetTimeout = helpers.IntSecondDefault(config.ResetTimeoutSec, 3*time.Second)
	errs := make([]error, 0, len(config.Tests))
	for _, t := range config.Tests {
		if d, err := g.Engine.ParseText(t.Name, t.Scenario); err != nil {
			errs = append(errs, err)
		} else {
			self.testList = append(self.testList, d)
		}
	}
	if err := helpers.FoldErrors(errs); err != nil {
		g.Log.Fatal(err)
	}
}

func (self *UI) onServiceBegin(ctx context.Context) State {
	self.inputBuf = self.inputBuf[:0]
	self.lastActivity = time.Now()
	self.Service.askReport = false
	self.Service.menuIdx = 0
	self.Service.invIdx = 0
	self.Service.invList = make([]*inventory.Stock, 0, 16)
	self.Service.testIdx = 0
	self.g.Inventory.Iter(func(s *inventory.Stock) {
		self.g.Log.Debugf("ui service inventory: - %s", s.String())
		self.Service.invList = append(self.Service.invList, s)
	})
	sort.Slice(self.Service.invList, func(a, b int) bool {
		xa := self.Service.invList[a]
		xb := self.Service.invList[b]
		if xa.Code != xb.Code {
			return xa.Code < xb.Code
		}
		return xa.Name < xb.Name
	})
	// self.g.Log.Debugf("invlist=%v, invidx=%d", self.Service.invList, self.Service.invIdx)

	err := self.g.Engine.ExecList(ctx, "on_service_begin", self.g.Config.Engine.OnServiceBegin)
	if err != nil {
		self.g.Error(err)
		return StateBroken
	}

	self.g.Log.Debugf("ui service begin")
	self.g.Tele.State(tele_api.State_Service)
	return StateServiceAuth
}

func (self *UI) onServiceAuth() State {
	serviceConfig := &self.g.Config.UI.Service
	if !serviceConfig.Auth.Enable {
		return StateServiceMenu
	}

	passVisualHash := VisualHash(self.inputBuf, self.Service.SecretSalt)
	self.display.SetLines(
		serviceConfig.MsgAuth,
		fmt.Sprintf(msgServiceInputAuth, passVisualHash),
	)

	next, e := self.serviceWaitInput()
	if next != StateDefault {
		return next
	}

	switch {
	case e.IsDigit():
		self.inputBuf = append(self.inputBuf, byte(e.Key))
		if len(self.inputBuf) > 16 {
			self.display.SetLines(MsgError, "len") // FIXME extract message string
			self.serviceWaitInput()
			return StateServiceEnd
		}
		return self.State()

	case e.IsZero() || input.IsReject(&e):
		return StateServiceEnd

	case input.IsAccept(&e):
		if len(self.inputBuf) == 0 {
			self.display.SetLines(MsgError, "empty") // FIXME extract message string
			self.serviceWaitInput()
			return StateServiceEnd
		}

		// FIXME fnv->secure hash for actual password comparison
		inputHash := VisualHash(self.inputBuf, self.Service.SecretSalt)
		for i, p := range self.g.Config.UI.Service.Auth.Passwords {
			if inputHash == p {
				self.g.Log.Infof("service auth ok i=%d hash=%s", i, inputHash)
				return StateServiceMenu
			}
		}

		self.display.SetLines(MsgError, "sorry") // FIXME extract message string
		self.serviceWaitInput()
		return StateServiceEnd
	}
	self.g.Log.Errorf("ui onServiceAuth unhandled branch")
	self.display.SetLines(MsgError, "code error") // FIXME extract message string
	self.serviceWaitInput()
	return StateServiceEnd
}

func (self *UI) onServiceMenu() State {
	menuName := serviceMenu[self.Service.menuIdx]
	self.display.SetLines(
		msgServiceMenu,
		fmt.Sprintf("%d %s", self.Service.menuIdx+1, menuName),
	)

	next, e := self.serviceWaitInput()
	if next != StateDefault {
		return next
	}

	switch {
	case e.Key == input.EvendKeyCreamLess:
		self.Service.menuIdx = (self.Service.menuIdx + serviceMenuMax - 1) % (serviceMenuMax + 1)
	case e.Key == input.EvendKeyCreamMore:
		self.Service.menuIdx = (self.Service.menuIdx + 1) % (serviceMenuMax + 1)

	case input.IsAccept(&e):
		if int(self.Service.menuIdx) >= len(serviceMenu) {
			panic("code error service menuIdx out of range")
		}
		switch serviceMenu[self.Service.menuIdx] {
		case serviceMenuInventory:
			return StateServiceInventory
		case serviceMenuTest:
			return StateServiceTest
		case serviceMenuReboot:
			return StateServiceReboot
		case serviceMenuNetwork:
			return StateServiceNetwork
		default:
			panic("code error")
		}

	case input.IsReject(&e):
		return StateServiceEnd

	case e.IsDigit():
		x := byte(e.Key) - byte('0')
		if x > 0 && x <= serviceMenuMax {
			self.Service.menuIdx = x - 1
		}
	}
	return StateServiceMenu
}

func (self *UI) onServiceInventory() State {
	if len(self.Service.invList) == 0 {
		self.display.SetLines(MsgError, "inv empty") // FIXME extract message string
		self.serviceWaitInput()
		return StateServiceMenu
	}
	invCurrent := self.Service.invList[self.Service.invIdx]
	self.display.SetLines(
		fmt.Sprintf("I%d %s", invCurrent.Code, invCurrent.Name),
		fmt.Sprintf("%.1f %s\x00", invCurrent.Value(), string(self.inputBuf)), // TODO configurable decimal point
	)

	next, e := self.serviceWaitInput()
	if next != StateDefault {
		return next
	}

	invIdxMax := uint8(len(self.Service.invList))
	switch {
	case e.Key == input.EvendKeyCreamLess:
		self.Service.invIdx = (self.Service.invIdx + invIdxMax - 1) % invIdxMax
		self.inputBuf = self.inputBuf[:0]
	case e.Key == input.EvendKeyCreamMore:
		self.Service.invIdx = (self.Service.invIdx + 1) % invIdxMax
		self.inputBuf = self.inputBuf[:0]

	case e.Key == input.EvendKeyDot || e.IsDigit():
		self.inputBuf = append(self.inputBuf, byte(e.Key))

	case input.IsAccept(&e):
		if len(self.inputBuf) == 0 {
			self.g.Log.Errorf("ui onServiceInventory input=accept inputBuf=empty")
			self.display.SetLines(MsgError, "empty") // FIXME extract message string
			self.serviceWaitInput()
			return StateServiceInventory
		}

		x, err := strconv.ParseFloat(string(self.inputBuf), 32)
		self.inputBuf = self.inputBuf[:0]
		if err != nil {
			self.g.Log.Errorf("ui onServiceInventory input=accept inputBuf='%s'", string(self.inputBuf))
			self.display.SetLines(MsgError, "number-invalid") // FIXME extract message string
			self.serviceWaitInput()
			return StateServiceInventory
		}

		invCurrent := self.Service.invList[self.Service.invIdx]
		invCurrent.Set(float32(x))
		self.Service.askReport = true

	case input.IsReject(&e):
		// backspace semantic
		if len(self.inputBuf) > 0 {
			self.inputBuf = self.inputBuf[:len(self.inputBuf)-1]
			return StateServiceInventory
		}
		return StateServiceMenu
	}
	return StateServiceInventory
}

func (self *UI) onServiceTest(ctx context.Context) State {
	self.inputBuf = self.inputBuf[:0]
	if len(self.Service.testList) == 0 {
		self.display.SetLines(MsgError, "no tests") // FIXME extract message string
		self.serviceWaitInput()
		return StateServiceMenu
	}
	testCurrent := self.Service.testList[self.Service.testIdx]
	line1 := fmt.Sprintf("T%d %s", self.Service.testIdx+1, testCurrent.String())
	self.display.SetLines(line1, "")

wait:
	next, e := self.serviceWaitInput()
	if next != StateDefault {
		return next
	}

	testIdxMax := uint8(len(self.Service.testList))
	switch {
	case e.Key == input.EvendKeyCreamLess:
		self.Service.testIdx = (self.Service.testIdx + testIdxMax - 1) % testIdxMax
	case e.Key == input.EvendKeyCreamMore:
		self.Service.testIdx = (self.Service.testIdx + 1) % testIdxMax

	case input.IsAccept(&e):
		self.Service.askReport = true
		self.display.SetLines(line1, "in progress")
		err := testCurrent.Do(ctx)
		if err == nil {
			self.display.SetLines(line1, "OK")
		} else {
			self.g.Error(err)
			self.display.SetLines(line1, "error")
		}
		goto wait

	case input.IsReject(&e):
		return StateServiceMenu
	}
	return StateServiceTest
}

func (self *UI) onServiceReboot() State {
	self.display.SetLines("for reboot", "press 1") // FIXME extract message string

	next, e := self.serviceWaitInput()
	if next != StateDefault {
		return next
	}

	switch {
	case e.Key == '1':
		self.display.SetLines("reboot", "in progress") // FIXME extract message string
		// os.Exit(0)
		self.g.Alive.Stop()
		return StateServiceEnd
	}
	return StateServiceMenu
}

func (self *UI) onServiceNetwork() State {
	addrs, _ := net.InterfaceAddrs()
	// TODO filter
	listString := fmt.Sprintf("%v", addrs)
	self.display.SetLines("network", listString)

	for {
		next, e := self.serviceWaitInput()
		if next != StateDefault {
			return next
		}
		if input.IsReject(&e) {
			return StateServiceMenu
		}
	}
}

func (self *UI) onServiceReport(ctx context.Context) State {
	_ = self.g.Tele.Report(ctx, true)
	if err := self.g.Engine.ExecList(ctx, "service-report", []string{"money.cashbox_zero"}); err != nil {
		self.g.Error(err)
	}
	return StateDefault
}

func (self *UI) onServiceEnd(ctx context.Context) State {
	_ = self.g.Inventory.Persist.Store()
	self.inputBuf = self.inputBuf[:0]

	if self.Service.askReport {
		self.display.SetLines("for tele report", "press 1") // FIXME extract message string
		if e := self.wait(self.Service.resetTimeout); e.Kind == EventInput && e.Input.Key == '1' {
			self.Service.askReport = false
			self.onServiceReport(ctx)
		}
	}

	err := self.g.Engine.ExecList(ctx, "on_service_end", self.g.Config.Engine.OnServiceEnd)
	if err != nil {
		self.g.Error(err)
		return StateBroken
	}
	return StateDefault
}

func (self *UI) serviceWaitInput() (State, input.Event) {
	e := self.wait(self.Service.resetTimeout)
	switch e.Kind {
	case EventInput:
		return StateDefault, e.Input

	case EventMoney:
		self.g.Log.Debugf("serviceWaitInput money event=%v", e.Money)
		return StateDefault, input.Event{}

	case EventTime:
		// self.g.Log.Infof("inactive=%v", inactive)
		self.g.Log.Debugf("serviceWaitInput resetTimeout")
		return StateServiceEnd, input.Event{}

	case EventLock:
		return StateLocked, input.Event{}

	case EventStop:
		self.g.Log.Debugf("serviceWaitInput global stop")
		return StateServiceEnd, input.Event{}

	default:
		panic(fmt.Sprintf("code error serviceWaitInput unhandled event=%#v", e))
	}
}

func VisualHash(input, salt []byte) string {
	h := fnv.New32()
	_, _ = h.Write(salt)
	_, _ = h.Write(input)
	_, _ = h.Write(salt)
	var buf [4]byte
	binary := h.Sum(buf[:0])
	b64 := base64.RawStdEncoding.EncodeToString(binary)
	return strings.ToLower(b64)
}
<commit_msg>service: (network) hide loopback addresses<commit_after>package ui

import (
	"context"
	"encoding/base64"
	"fmt"
	"hash/fnv"
	"net"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/temoto/alive"
	"github.com/temoto/vender/currency"
	"github.com/temoto/vender/engine"
	"github.com/temoto/vender/engine/inventory"
	"github.com/temoto/vender/hardware/input"
	"github.com/temoto/vender/head/money"
	tele_api "github.com/temoto/vender/head/tele/api"
	"github.com/temoto/vender/helpers"
	"github.com/temoto/vender/state"
)

const (
	serviceMenuInventory = "inventory"
	serviceMenuTest      = "test"
	serviceMenuReboot    = "reboot"
	serviceMenuNetwork   = "network"
	serviceMenuReport    = "report"
)

var /*const*/ serviceMenu = []string{
	serviceMenuInventory,
	serviceMenuTest,
	serviceMenuReboot,
	serviceMenuNetwork,
	serviceMenuReport,
}
var /*const*/ serviceMenuMax = uint8(len(serviceMenu) - 1)

type uiService struct {
	// config
	resetTimeout time.Duration
	SecretSalt   []byte

	// state
	askReport bool
	menuIdx   uint8
	invIdx    uint8
	invList   []*inventory.Stock
	testIdx   uint8
	testList  []engine.Doer
}

func (self *uiService) Init(ctx context.Context) {
	g := state.GetGlobal(ctx)
	config := g.Config.UI.Service
	self.SecretSalt = []byte{0} // FIXME read from config
	self.resetTimeout = helpers.IntSecondDefault(config.ResetTimeoutSec, 3*time.Second)
	errs := make([]error, 0, len(config.Tests))
	for _, t := range config.Tests {
		if d, err := g.Engine.ParseText(t.Name, t.Scenario); err != nil {
			errs = append(errs, err)
		} else {
			self.testList = append(self.testList, d)
		}
	}
	if err := helpers.FoldErrors(errs); err != nil {
		g.Log.Fatal(err)
	}
}

func (self *UI) onServiceBegin(ctx context.Context) State {
	self.inputBuf = self.inputBuf[:0]
	self.lastActivity = time.Now()
	self.Service.askReport = false
	self.Service.menuIdx = 0
	self.Service.invIdx = 0
	self.Service.invList = make([]*inventory.Stock, 0, 16)
	self.Service.testIdx = 0
	self.g.Inventory.Iter(func(s *inventory.Stock) {
		self.g.Log.Debugf("ui service inventory: - %s", s.String())
		self.Service.invList = append(self.Service.invList, s)
	})
	sort.Slice(self.Service.invList, func(a, b int) bool {
		xa := self.Service.invList[a]
		xb := self.Service.invList[b]
		if xa.Code != xb.Code {
			return xa.Code < xb.Code
		}
		return xa.Name < xb.Name
	})
	// self.g.Log.Debugf("invlist=%v, invidx=%d", self.Service.invList, self.Service.invIdx)

	err := self.g.Engine.ExecList(ctx, "on_service_begin", self.g.Config.Engine.OnServiceBegin)
	if err != nil {
		self.g.Error(err)
		return StateBroken
	}

	self.g.Log.Debugf("ui service begin")
	self.g.Tele.State(tele_api.State_Service)
	return StateServiceAuth
}

func (self *UI) onServiceAuth() State {
	serviceConfig := &self.g.Config.UI.Service
	if !serviceConfig.Auth.Enable {
		return StateServiceMenu
	}

	passVisualHash := VisualHash(self.inputBuf, self.Service.SecretSalt)
	self.display.SetLines(
		serviceConfig.MsgAuth,
		fmt.Sprintf(msgServiceInputAuth, passVisualHash),
	)

	next, e := self.serviceWaitInput()
	if next != StateDefault {
		return next
	}

	switch {
	case e.IsDigit():
		self.inputBuf = append(self.inputBuf, byte(e.Key))
		if len(self.inputBuf) > 16 {
			self.display.SetLines(MsgError, "len") // FIXME extract message string
			self.serviceWaitInput()
			return StateServiceEnd
		}
		return self.State()

	case e.IsZero() || input.IsReject(&e):
		return StateServiceEnd

	case input.IsAccept(&e):
		if len(self.inputBuf) == 0 {
			self.display.SetLines(MsgError, "empty") // FIXME extract message string
			self.serviceWaitInput()
			return StateServiceEnd
		}

		// FIXME fnv->secure hash for actual password comparison
		inputHash := VisualHash(self.inputBuf, self.Service.SecretSalt)
		for i, p := range self.g.Config.UI.Service.Auth.Passwords {
			if inputHash == p {
				self.g.Log.Infof("service auth ok i=%d hash=%s", i, inputHash)
				return StateServiceMenu
			}
		}

		self.display.SetLines(MsgError, "sorry") // FIXME extract message string
		self.serviceWaitInput()
		return StateServiceEnd
	}
	self.g.Log.Errorf("ui onServiceAuth unhandled branch")
	self.display.SetLines(MsgError, "code error") // FIXME extract message string
	self.serviceWaitInput()
	return StateServiceEnd
}

func (self *UI) onServiceMenu() State {
	menuName := serviceMenu[self.Service.menuIdx]
	self.display.SetLines(
		msgServiceMenu,
		fmt.Sprintf("%d %s", self.Service.menuIdx+1, menuName),
	)

	next, e := self.serviceWaitInput()
	if next != StateDefault {
		return next
	}

	switch {
	case e.Key == input.EvendKeyCreamLess:
		self.Service.menuIdx = (self.Service.menuIdx + serviceMenuMax - 1) % (serviceMenuMax + 1)
	case e.Key == input.EvendKeyCreamMore:
		self.Service.menuIdx = (self.Service.menuIdx + 1) % (serviceMenuMax + 1)

	case input.IsAccept(&e):
		if int(self.Service.menuIdx) >= len(serviceMenu) {
			panic("code error service menuIdx out of range")
		}
		switch serviceMenu[self.Service.menuIdx] {
		case serviceMenuInventory:
			return StateServiceInventory
		case serviceMenuTest:
			return StateServiceTest
		case serviceMenuReboot:
			return StateServiceReboot
		case serviceMenuNetwork:
			return StateServiceNetwork
		default:
			panic("code error")
		}

	case input.IsReject(&e):
		return StateServiceEnd

	case e.IsDigit():
		x := byte(e.Key) - byte('0')
		if x > 0 && x <= serviceMenuMax {
			self.Service.menuIdx = x - 1
		}
	}
	return StateServiceMenu
}

func (self *UI) onServiceInventory() State {
	if len(self.Service.invList) == 0 {
		self.display.SetLines(MsgError, "inv empty") // FIXME extract message string
		self.serviceWaitInput()
		return StateServiceMenu
	}
	invCurrent := self.Service.invList[self.Service.invIdx]
	self.display.SetLines(
		fmt.Sprintf("I%d %s", invCurrent.Code, invCurrent.Name),
		fmt.Sprintf("%.1f %s\x00", invCurrent.Value(), string(self.inputBuf)), // TODO configurable decimal point
	)

	next, e := self.serviceWaitInput()
	if next != StateDefault {
		return next
	}

	invIdxMax := uint8(len(self.Service.invList))
	switch {
	case e.Key == input.EvendKeyCreamLess:
		self.Service.invIdx = (self.Service.invIdx + invIdxMax - 1) % invIdxMax
		self.inputBuf = self.inputBuf[:0]
	case e.Key == input.EvendKeyCreamMore:
		self.Service.invIdx = (self.Service.invIdx + 1) % invIdxMax
		self.inputBuf = self.inputBuf[:0]

	case e.Key == input.EvendKeyDot || e.IsDigit():
		self.inputBuf = append(self.inputBuf, byte(e.Key))

	case input.IsAccept(&e):
		if len(self.inputBuf) == 0 {
			self.g.Log.Errorf("ui onServiceInventory input=accept inputBuf=empty")
			self.display.SetLines(MsgError, "empty") // FIXME extract message string
			self.serviceWaitInput()
			return StateServiceInventory
		}

		x, err := strconv.ParseFloat(string(self.inputBuf), 32)
		self.inputBuf = self.inputBuf[:0]
		if err != nil {
			self.g.Log.Errorf("ui onServiceInventory input=accept inputBuf='%s'", string(self.inputBuf))
			self.display.SetLines(MsgError, "number-invalid") // FIXME extract message string
			self.serviceWaitInput()
			return StateServiceInventory
		}

		invCurrent := self.Service.invList[self.Service.invIdx]
		invCurrent.Set(float32(x))
		self.Service.askReport = true

	case input.IsReject(&e):
		// backspace semantic
		if len(self.inputBuf) > 0 {
			self.inputBuf = self.inputBuf[:len(self.inputBuf)-1]
			return StateServiceInventory
		}
		return StateServiceMenu
	}
	return StateServiceInventory
}

func (self *UI) onServiceTest(ctx context.Context) State {
	self.inputBuf = self.inputBuf[:0]
	if len(self.Service.testList) == 0 {
		self.display.SetLines(MsgError, "no tests") // FIXME extract message string
		self.serviceWaitInput()
		return StateServiceMenu
	}
	testCurrent := self.Service.testList[self.Service.testIdx]
	line1 := fmt.Sprintf("T%d %s", self.Service.testIdx+1, testCurrent.String())
	self.display.SetLines(line1, "")

wait:
	next, e := self.serviceWaitInput()
	if next != StateDefault {
		return next
	}

	testIdxMax := uint8(len(self.Service.testList))
	switch {
	case e.Key == input.EvendKeyCreamLess:
		self.Service.testIdx = (self.Service.testIdx + testIdxMax - 1) % testIdxMax
	case e.Key == input.EvendKeyCreamMore:
		self.Service.testIdx = (self.Service.testIdx + 1) % testIdxMax

	case input.IsAccept(&e):
		self.Service.askReport = true
		self.display.SetLines(line1, "in progress")
		err := testCurrent.Do(ctx)
		if err == nil {
			self.display.SetLines(line1, "OK")
		} else {
			self.g.Error(err)
			self.display.SetLines(line1, "error")
		}
		goto wait

	case input.IsReject(&e):
		return StateServiceMenu
	}
	return StateServiceTest
}

func (self *UI) onServiceReboot() State {
	self.display.SetLines("for reboot", "press 1") // FIXME extract message string

	next, e := self.serviceWaitInput()
	if next != StateDefault {
		return next
	}

	switch {
	case e.Key == '1':
		self.display.SetLines("reboot", "in progress") // FIXME extract message string
		// os.Exit(0)
		self.g.Alive.Stop()
		return StateServiceEnd
	}
	return StateServiceMenu
}

func (self *UI) onServiceNetwork() State {
	allAddrs, _ := net.InterfaceAddrs()
	addrs := make([]string, 0, len(allAddrs))
	// TODO parse ignored networks from config
addrLoop:
	for _, addr := range allAddrs {
		ip, _, err := net.ParseCIDR(addr.String())
		if err != nil {
			self.g.Log.Errorf("invalid local addr=%v", addr)
			continue addrLoop
		}
		if ip.IsLoopback() {
			continue addrLoop
		}
		addrs = append(addrs, ip.String())
	}
	listString := strings.Join(addrs, " ")
	self.display.SetLines("network", listString)

	for {
		next, e := self.serviceWaitInput()
		if next != StateDefault {
			return next
		}
		if input.IsReject(&e) {
			return StateServiceMenu
		}
	}
}

func (self *UI) onServiceReport(ctx context.Context) State {
	_ = self.g.Tele.Report(ctx, true)
	if err := self.g.Engine.ExecList(ctx, "service-report", []string{"money.cashbox_zero"}); err != nil {
		self.g.Error(err)
	}
	return StateDefault
}

func (self *UI) onServiceEnd(ctx context.Context) State {
	_ = self.g.Inventory.Persist.Store()
	self.inputBuf = self.inputBuf[:0]

	if self.Service.askReport {
		self.display.SetLines("for tele report", "press 1") // FIXME extract message string
		if e := self.wait(self.Service.resetTimeout); e.Kind == EventInput && e.Input.Key == '1' {
			self.Service.askReport = false
			self.onServiceReport(ctx)
		}
	}

	err := self.g.Engine.ExecList(ctx, "on_service_end", self.g.Config.Engine.OnServiceEnd)
	if err != nil {
		self.g.Error(err)
		return StateBroken
	}
	return StateDefault
}

func (self *UI) serviceWaitInput() (State, input.Event) {
	e := self.wait(self.Service.resetTimeout)
	switch e.Kind {
	case EventInput:
		return StateDefault, e.Input

	case EventMoney:
		self.g.Log.Debugf("serviceWaitInput money event=%v", e.Money)
		return StateDefault, input.Event{}

	case EventTime:
		// self.g.Log.Infof("inactive=%v", inactive)
		self.g.Log.Debugf("serviceWaitInput resetTimeout")
		return StateServiceEnd, input.Event{}

	case EventLock:
		return StateLocked, input.Event{}

	case EventStop:
		self.g.Log.Debugf("serviceWaitInput global stop")
		return StateServiceEnd, input.Event{}

	default:
		panic(fmt.Sprintf("code error serviceWaitInput unhandled event=%#v", e))
	}
}

func VisualHash(input, salt []byte) string {
	h := fnv.New32()
	_, _ = h.Write(salt)
	_, _ = h.Write(input)
	_, _ = h.Write(salt)
	var buf [4]byte
	binary := h.Sum(buf[:0])
	b64 := base64.RawStdEncoding.EncodeToString(binary)
	return strings.ToLower(b64)
}
<|endoftext|>


<commit_before>// Package tasks provides functions for scheduling
// periodic tasks (e.g. background jobs).
package tasks

import (
	"bytes"
	"errors"
	"fmt"
	"gnd.la/app"
	"gnd.la/log"
	"gnd.la/signal"
	"gnd.la/util/internal/runtimeutil"
	"reflect"
	"sync"
	"time"
)

var running struct {
	sync.Mutex
	tasks map[*Task]int
}

var registered struct {
	sync.RWMutex
	tasks map[string]*Task
}

// Task represent a scheduled task.
type Task struct {
	App      *app.App
	Handler  app.Handler
	Interval time.Duration
	Options  *Options
	ticker   *time.Ticker
}

// Stop de-schedules the task. After stopping the task, it
// won't be started again but if it's currently running, it will
// be completed.
func (t *Task) Stop() {
	if t.ticker != nil {
		t.ticker.Stop()
		t.ticker = nil
	}
}

func (t *Task) Resume(now bool) {
	t.Stop()
	t.ticker = time.NewTicker(t.Interval)
	go t.execute(now)
}

// Name returns the task name.
func (t *Task) Name() string {
	if t.Options != nil && t.Options.Name != "" {
		return t.Options.Name
	}
	return runtimeutil.FuncName(t.Handler)
}

// Delete stops the task by calling t.Stop() and then removes
// it from the internal task register.
func (t *Task) Delete() {
	registered.Lock()
	defer registered.Unlock()
	t.deleteLocked()
}

func (t *Task) deleteLocked() {
	t.Stop()
	delete(registered.tasks, t.Name())
}

func (t *Task) execute(now bool) {
	if now {
		executeTask(t)
	}
	for _ = range t.ticker.C {
		executeTask(t)
	}
}

// Options are used to specify task options when registering them.
type Options struct {
	// Name indicates the task name, used for checking the number
	// of instances running. If the task name is not provided, it's
	// derived from the function. Two tasks with the same name are
	// considered as equal, even if their functions are different.
	Name string
	// MaxInstances indicates the maximum number of instances of
	// this function that can be simultaneously running. If zero,
	// there is no limit.
	MaxInstances int
}

func afterTask(task *Task, started time.Time, terr *error) {
	name := task.Name()
	if err := recover(); err != nil {
		skip, stackSkip, _, _ := runtimeutil.GetPanic()
		var buf bytes.Buffer
		buf.WriteString(fmt.Sprintf("Panic executing task %s: ", name))
		buf.WriteString(fmt.Sprintf("%v", err))
		buf.WriteByte('\n')
		stack := runtimeutil.FormatStack(stackSkip)
		location, code := runtimeutil.FormatCaller(skip, 5, true, true)
		if location != "" {
			buf.WriteString("\n At ")
			buf.WriteString(location)
			if code != "" {
				buf.WriteByte('\n')
				buf.WriteString(code)
				buf.WriteByte('\n')
			}
		}
		if stack != "" {
			buf.WriteString("\nStack:\n")
			buf.WriteString(stack)
		}
		*terr = errors.New(buf.String())
	}
	end := time.Now()
	log.Debugf("Finished task %s at %v (took %v)", name, end, end.Sub(started))
	running.Lock()
	defer running.Unlock()
	c := running.tasks[task]
	if c > 1 {
		running.tasks[task] = c - 1
	} else {
		delete(running.tasks, task)
	}
}

func canRunTask(task *Task) error {
	running.Lock()
	defer running.Unlock()
	c := running.tasks[task]
	if task.Options != nil && task.Options.MaxInstances > 0 {
		if c >= task.Options.MaxInstances {
			return fmt.Errorf("not starting task %s because it's already running %d instances", task.Name(), c)
		}
	}
	if running.tasks == nil {
		running.tasks = make(map[*Task]int)
	}
	running.tasks[task] = c + 1
	return nil
}

func executeTask(task *Task) (bool, error) {
	if err := canRunTask(task); err != nil {
		return false, err
	}
	ctx := task.App.NewContext(contextProvider(0))
	defer task.App.CloseContext(ctx)
	started := time.Now()
	log.Debugf("Starting task %s at %v", task.Name(), started)
	var err error
	defer afterTask(task, started, &err)
	task.Handler(ctx)
	return true, err
}

// Register registers a new task that might be run with Run, but
// without scheduling it. If there was previously another task
// registered with the same name, it will be deleted.
func Register(m *app.App, task app.Handler, opts *Options) *Task {
	t := &Task{App: m, Handler: task, Options: opts}
	registered.Lock()
	defer registered.Unlock()
	if registered.tasks == nil {
		registered.tasks = make(map[string]*Task)
	}
	name := t.Name()
	if prev := registered.tasks[name]; prev != nil {
		log.Debugf("There's already a task registered as %s, deleting it", name)
		prev.deleteLocked()
	}
	registered.tasks[name] = t
	return t
}

// Schedule registers and schedules a task to be run at the given
// interval. If interval is 0, the task is only registered, but not
// scheduled. The now argument indicates if the task should also run
// right now (in a goroutine) rather than waiting until interval for
// the first run. Schedule returns a Task instance, which might be
// used to stop, resume or delete a it.
func Schedule(m *app.App, task app.Handler, opts *Options, interval time.Duration, now bool) *Task {
	t := Register(m, task, opts)
	t.Interval = interval
	signal.Emit(signal.TASKS_WILL_SCHEDULE_TASK, t)
	go t.Resume(now)
	return t
}

// Run starts the given task identifier by it's name, unless
// it has been previously registered with Options which
// prevent from running it right now (e.g. it was registered
// with MaxInstances = 2 and there are already 2 instances running).
// The first return argument indicates if the task was executed, while
// the second includes any errors which happened while running the task.
func Run(name string) (bool, error) {
	registered.RLock()
	task := registered.tasks[name]
	registered.RUnlock()
	if task == nil {
		return false, fmt.Errorf("there's no task registered with the name %q", name)
	}
	return executeTask(task)
}

// RunHandler starts the given task identifier by it's handler. The same
// restrictions in Run() apply to this function.
// Return values are the same as Run().
func RunHandler(handler app.Handler) (bool, error) {
	var task *Task
	p := reflect.ValueOf(handler).Pointer()
	registered.RLock()
	for _, v := range registered.tasks {
		if reflect.ValueOf(v.Handler).Pointer() == p {
			task = v
			break
		}
	}
	registered.RUnlock()
	if task == nil {
		return false, fmt.Errorf("there's no task registered with the handler %s", runtimeutil.FuncName(handler))
	}
	return executeTask(task)
}

// Execute runs the given handler in a task context. If the handler fails
// with a panic, it will be returned in the error return value.
func Execute(a *app.App, handler app.Handler) error {
	t := &Task{App: a, Handler: handler}
	_, err := executeTask(t)
	return err
}
<commit_msg>Log errors from tasks<commit_after>// Package tasks provides functions for scheduling
// periodic tasks (e.g. background jobs).
package tasks

import (
	"bytes"
	"errors"
	"fmt"
	"gnd.la/app"
	"gnd.la/log"
	"gnd.la/signal"
	"gnd.la/util/internal/runtimeutil"
	"reflect"
	"sync"
	"time"
)

var running struct {
	sync.Mutex
	tasks map[*Task]int
}

var registered struct {
	sync.RWMutex
	tasks map[string]*Task
}

// Task represent a scheduled task.
type Task struct {
	App      *app.App
	Handler  app.Handler
	Interval time.Duration
	Options  *Options
	ticker   *time.Ticker
}

// Stop de-schedules the task. After stopping the task, it
// won't be started again but if it's currently running, it will
// be completed.
func (t *Task) Stop() {
	if t.ticker != nil {
		t.ticker.Stop()
		t.ticker = nil
	}
}

func (t *Task) Resume(now bool) {
	t.Stop()
	t.ticker = time.NewTicker(t.Interval)
	go t.execute(now)
}

// Name returns the task name.
func (t *Task) Name() string {
	if t.Options != nil && t.Options.Name != "" {
		return t.Options.Name
	}
	return runtimeutil.FuncName(t.Handler)
}

// Delete stops the task by calling t.Stop() and then removes
// it from the internal task register.
func (t *Task) Delete() {
	registered.Lock()
	defer registered.Unlock()
	t.deleteLocked()
}

func (t *Task) deleteLocked() {
	t.Stop()
	delete(registered.tasks, t.Name())
}

func (t *Task) execute(now bool) {
	if now {
		t.executeTask()
	}
	for _ = range t.ticker.C {
		t.executeTask()
	}
}

func (t *Task) executeTask() {
	if _, err := executeTask(t); err != nil {
		log.Error(err)
	}
}

// Options are used to specify task options when registering them.
type Options struct {
	// Name indicates the task name, used for checking the number
	// of instances running. If the task name is not provided, it's
	// derived from the function. Two tasks with the same name are
	// considered as equal, even if their functions are different.
	Name string
	// MaxInstances indicates the maximum number of instances of
	// this function that can be simultaneously running. If zero,
	// there is no limit.
	MaxInstances int
}

func afterTask(task *Task, started time.Time, terr *error) {
	name := task.Name()
	if err := recover(); err != nil {
		skip, stackSkip, _, _ := runtimeutil.GetPanic()
		var buf bytes.Buffer
		fmt.Fprintf(&buf, "Panic executing task %s: %v\n", name, err)
		stack := runtimeutil.FormatStack(stackSkip)
		location, code := runtimeutil.FormatCaller(skip, 5, true, true)
		if location != "" {
			buf.WriteString("\n At ")
			buf.WriteString(location)
			if code != "" {
				buf.WriteByte('\n')
				buf.WriteString(code)
				buf.WriteByte('\n')
			}
		}
		if stack != "" {
			buf.WriteString("\nStack:\n")
			buf.WriteString(stack)
		}
		*terr = errors.New(buf.String())
	}
	end := time.Now()
	log.Debugf("Finished task %s at %v (took %v)", name, end, end.Sub(started))
	running.Lock()
	defer running.Unlock()
	c := running.tasks[task]
	if c > 1 {
		running.tasks[task] = c - 1
	} else {
		delete(running.tasks, task)
	}
}

func canRunTask(task *Task) error {
	running.Lock()
	defer running.Unlock()
	c := running.tasks[task]
	if task.Options != nil && task.Options.MaxInstances > 0 {
		if c >= task.Options.MaxInstances {
			return fmt.Errorf("not starting task %s because it's already running %d instances", task.Name(), c)
		}
	}
	if running.tasks == nil {
		running.tasks = make(map[*Task]int)
	}
	running.tasks[task] = c + 1
	return nil
}

func executeTask(task *Task) (ran bool, err error) {
	if err = canRunTask(task); err != nil {
		return
	}
	ctx := task.App.NewContext(contextProvider(0))
	defer task.App.CloseContext(ctx)
	started := time.Now()
	log.Debugf("Starting task %s at %v", task.Name(), started)
	ran = true
	defer afterTask(task, started, &err)
	task.Handler(ctx)
	return
}

// Register registers a new task that might be run with Run, but
// without scheduling it. If there was previously another task
// registered with the same name, it will be deleted.
func Register(m *app.App, task app.Handler, opts *Options) *Task {
	t := &Task{App: m, Handler: task, Options: opts}
	registered.Lock()
	defer registered.Unlock()
	if registered.tasks == nil {
		registered.tasks = make(map[string]*Task)
	}
	name := t.Name()
	if prev := registered.tasks[name]; prev != nil {
		log.Debugf("There's already a task registered as %s, deleting it", name)
		prev.deleteLocked()
	}
	registered.tasks[name] = t
	return t
}

// Schedule registers and schedules a task to be run at the given
// interval. If interval is 0, the task is only registered, but not
// scheduled. The now argument indicates if the task should also run
// right now (in a goroutine) rather than waiting until interval for
// the first run. Schedule returns a Task instance, which might be
// used to stop, resume or delete a it.
func Schedule(m *app.App, task app.Handler, opts *Options, interval time.Duration, now bool) *Task {
	t := Register(m, task, opts)
	t.Interval = interval
	signal.Emit(signal.TASKS_WILL_SCHEDULE_TASK, t)
	go t.Resume(now)
	return t
}

// Run starts the given task identifier by it's name, unless
// it has been previously registered with Options which
// prevent from running it right now (e.g. it was registered
// with MaxInstances = 2 and there are already 2 instances running).
// The first return argument indicates if the task was executed, while
// the second includes any errors which happened while running the task.
func Run(name string) (bool, error) {
	registered.RLock()
	task := registered.tasks[name]
	registered.RUnlock()
	if task == nil {
		return false, fmt.Errorf("there's no task registered with the name %q", name)
	}
	return executeTask(task)
}

// RunHandler starts the given task identifier by it's handler. The same
// restrictions in Run() apply to this function.
// Return values are the same as Run().
func RunHandler(handler app.Handler) (bool, error) {
	var task *Task
	p := reflect.ValueOf(handler).Pointer()
	registered.RLock()
	for _, v := range registered.tasks {
		if reflect.ValueOf(v.Handler).Pointer() == p {
			task = v
			break
		}
	}
	registered.RUnlock()
	if task == nil {
		return false, fmt.Errorf("there's no task registered with the handler %s", runtimeutil.FuncName(handler))
	}
	return executeTask(task)
}

// Execute runs the given handler in a task context. If the handler fails
// with a panic, it will be returned in the error return value.
func Execute(a *app.App, handler app.Handler) error {
	t := &Task{App: a, Handler: handler}
	_, err := executeTask(t)
	return err
}
<|endoftext|>


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

// +build plan9

package runtime

import "runtime/internal/atomic"

var netpollInited uint32
var netpollWaiters uint32

var netpollStubLock mutex
var netpollNote note

// netpollBroken, protected by netpollBrokenLock, avoids a double notewakeup.
var netpollBrokenLock mutex
var netpollBroken bool

func netpollGenericInit() {
	atomic.Store(&netpollInited, 1)
}

func netpollBreak() {
	lock(&netpollBrokenLock)
	broken := netpollBroken
	netpollBroken = true
	if !broken {
		notewakeup(&netpollNote)
	}
	unlock(&netpollBrokenLock)
}

// Polls for ready network connections.
// Returns list of goroutines that become runnable.
func netpoll(delay int64) gList {
	// Implementation for platforms that do not support
	// integrated network poller.
	if delay != 0 {
		// This lock ensures that only one goroutine tries to use
		// the note. It should normally be completely uncontended.
		lock(&netpollStubLock)

		lock(&netpollBrokenLock)
		noteclear(&netpollNote)
		netpollBroken = false
		unlock(&netpollBrokenLock)

		notetsleep(&netpollNote, delay)
		unlock(&netpollStubLock)
	}
	return gList{}
}

func netpollinited() bool {
	return atomic.Load(&netpollInited) != 0
}
<commit_msg>runtime: avoid lock starvation in TestNetpollBreak on Plan 9<commit_after>// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build plan9

package runtime

import "runtime/internal/atomic"

var netpollInited uint32
var netpollWaiters uint32

var netpollStubLock mutex
var netpollNote note

// netpollBroken, protected by netpollBrokenLock, avoids a double notewakeup.
var netpollBrokenLock mutex
var netpollBroken bool

func netpollGenericInit() {
	atomic.Store(&netpollInited, 1)
}

func netpollBreak() {
	lock(&netpollBrokenLock)
	broken := netpollBroken
	netpollBroken = true
	if !broken {
		notewakeup(&netpollNote)
	}
	unlock(&netpollBrokenLock)
}

// Polls for ready network connections.
// Returns list of goroutines that become runnable.
func netpoll(delay int64) gList {
	// Implementation for platforms that do not support
	// integrated network poller.
	if delay != 0 {
		// This lock ensures that only one goroutine tries to use
		// the note. It should normally be completely uncontended.
		lock(&netpollStubLock)

		lock(&netpollBrokenLock)
		noteclear(&netpollNote)
		netpollBroken = false
		unlock(&netpollBrokenLock)

		notetsleep(&netpollNote, delay)
		unlock(&netpollStubLock)
		// Guard against starvation in case the lock is contended
		// (eg when running TestNetpollBreak).
		osyield()
	}
	return gList{}
}

func netpollinited() bool {
	return atomic.Load(&netpollInited) != 0
}
<|endoftext|>


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

package runtime_test

import (
	"bytes"
	"internal/testenv"
	"io"
	"os/exec"
	. "runtime"
	"runtime/debug"
	"strings"
	"testing"
	"unsafe"
)

func init() {
	// We're testing the runtime, so make tracebacks show things
	// in the runtime. This only raises the level, so it won't
	// override GOTRACEBACK=crash from the user.
	SetTracebackEnv("system")
}

var errf error

func errfn() error {
	return errf
}

func errfn1() error {
	return io.EOF
}

func BenchmarkIfaceCmp100(b *testing.B) {
	for i := 0; i < b.N; i++ {
		for j := 0; j < 100; j++ {
			if errfn() == io.EOF {
				b.Fatal("bad comparison")
			}
		}
	}
}

func BenchmarkIfaceCmpNil100(b *testing.B) {
	for i := 0; i < b.N; i++ {
		for j := 0; j < 100; j++ {
			if errfn1() == nil {
				b.Fatal("bad comparison")
			}
		}
	}
}

var efaceCmp1 interface{}
var efaceCmp2 interface{}

func BenchmarkEfaceCmpDiff(b *testing.B) {
	x := 5
	efaceCmp1 = &x
	y := 6
	efaceCmp2 = &y
	for i := 0; i < b.N; i++ {
		for j := 0; j < 100; j++ {
			if efaceCmp1 == efaceCmp2 {
				b.Fatal("bad comparison")
			}
		}
	}
}

func BenchmarkDefer(b *testing.B) {
	for i := 0; i < b.N; i++ {
		defer1()
	}
}

func defer1() {
	defer func(x, y, z int) {
		if recover() != nil || x != 1 || y != 2 || z != 3 {
			panic("bad recover")
		}
	}(1, 2, 3)
}

func BenchmarkDefer10(b *testing.B) {
	for i := 0; i < b.N/10; i++ {
		defer2()
	}
}

func defer2() {
	for i := 0; i < 10; i++ {
		defer func(x, y, z int) {
			if recover() != nil || x != 1 || y != 2 || z != 3 {
				panic("bad recover")
			}
		}(1, 2, 3)
	}
}

func BenchmarkDeferMany(b *testing.B) {
	for i := 0; i < b.N; i++ {
		defer func(x, y, z int) {
			if recover() != nil || x != 1 || y != 2 || z != 3 {
				panic("bad recover")
			}
		}(1, 2, 3)
	}
}

// golang.org/issue/7063
func TestStopCPUProfilingWithProfilerOff(t *testing.T) {
	SetCPUProfileRate(0)
}

// Addresses to test for faulting behavior.
// This is less a test of SetPanicOnFault and more a check that
// the operating system and the runtime can process these faults
// correctly. That is, we're indirectly testing that without SetPanicOnFault
// these would manage to turn into ordinary crashes.
// Note that these are truncated on 32-bit systems, so the bottom 32 bits
// of the larger addresses must themselves be invalid addresses.
// We might get unlucky and the OS might have mapped one of these
// addresses, but probably not: they're all in the first page, very high
// addresses that normally an OS would reserve for itself, or malformed
// addresses. Even so, we might have to remove one or two on different
// systems. We will see.

var faultAddrs = []uint64{
	// low addresses
	0,
	1,
	0xfff,
	// high (kernel) addresses
	// or else malformed.
	0xffffffffffffffff,
	0xfffffffffffff001,
	0xffffffffffff0001,
	0xfffffffffff00001,
	0xffffffffff000001,
	0xfffffffff0000001,
	0xffffffff00000001,
	0xfffffff000000001,
	0xffffff0000000001,
	0xfffff00000000001,
	0xffff000000000001,
	0xfff0000000000001,
	0xff00000000000001,
	0xf000000000000001,
	0x8000000000000001,
}

func TestSetPanicOnFault(t *testing.T) {
	old := debug.SetPanicOnFault(true)
	defer debug.SetPanicOnFault(old)

	nfault := 0
	for _, addr := range faultAddrs {
		testSetPanicOnFault(t, uintptr(addr), &nfault)
	}
	if nfault == 0 {
		t.Fatalf("none of the addresses faulted")
	}
}

func testSetPanicOnFault(t *testing.T, addr uintptr, nfault *int) {
	if GOOS == "nacl" {
		t.Skip("nacl doesn't seem to fault on high addresses")
	}

	defer func() {
		if err := recover(); err != nil {
			*nfault++
		}
	}()

	// The read should fault, except that sometimes we hit
	// addresses that have had C or kernel pages mapped there
	// readable by user code. So just log the content.
	// If no addresses fault, we'll fail the test.
	v := *(*byte)(unsafe.Pointer(addr))
	t.Logf("addr %#x: %#x\n", addr, v)
}

func eqstring_generic(s1, s2 string) bool {
	if len(s1) != len(s2) {
		return false
	}
	// optimization in assembly versions:
	// if s1.str == s2.str { return true }
	for i := 0; i < len(s1); i++ {
		if s1[i] != s2[i] {
			return false
		}
	}
	return true
}

func TestEqString(t *testing.T) {
	// This isn't really an exhaustive test of == on strings, it's
	// just a convenient way of documenting (via eqstring_generic)
	// what == does.
	s := []string{
		"",
		"a",
		"c",
		"aaa",
		"ccc",
		"cccc"[:3], // same contents, different string
		"1234567890",
	}
	for _, s1 := range s {
		for _, s2 := range s {
			x := s1 == s2
			y := eqstring_generic(s1, s2)
			if x != y {
				t.Errorf(`("%s" == "%s") = %t, want %t`, s1, s2, x, y)
			}
		}
	}
}

func TestTrailingZero(t *testing.T) {
	// make sure we add padding for structs with trailing zero-sized fields
	type T1 struct {
		n int32
		z [0]byte
	}
	if unsafe.Sizeof(T1{}) != 8 {
		t.Errorf("sizeof(%#v)==%d, want 8", T1{}, unsafe.Sizeof(T1{}))
	}
	type T2 struct {
		n int64
		z struct{}
	}
	if unsafe.Sizeof(T2{}) != 8+unsafe.Sizeof(Uintreg(0)) {
		t.Errorf("sizeof(%#v)==%d, want %d", T2{}, unsafe.Sizeof(T2{}), 8+unsafe.Sizeof(Uintreg(0)))
	}
	type T3 struct {
		n byte
		z [4]struct{}
	}
	if unsafe.Sizeof(T3{}) != 2 {
		t.Errorf("sizeof(%#v)==%d, want 2", T3{}, unsafe.Sizeof(T3{}))
	}
	// make sure padding can double for both zerosize and alignment
	type T4 struct {
		a int32
		b int16
		c int8
		z struct{}
	}
	if unsafe.Sizeof(T4{}) != 8 {
		t.Errorf("sizeof(%#v)==%d, want 8", T4{}, unsafe.Sizeof(T4{}))
	}
	// make sure we don't pad a zero-sized thing
	type T5 struct {
	}
	if unsafe.Sizeof(T5{}) != 0 {
		t.Errorf("sizeof(%#v)==%d, want 0", T5{}, unsafe.Sizeof(T5{}))
	}
}

func TestBadOpen(t *testing.T) {
	if GOOS == "windows" || GOOS == "nacl" {
		t.Skip("skipping OS that doesn't have open/read/write/close")
	}
	// make sure we get the correct error code if open fails. Same for
	// read/write/close on the resulting -1 fd. See issue 10052.
	nonfile := []byte("/notreallyafile")
	fd := Open(&nonfile[0], 0, 0)
	if fd != -1 {
		t.Errorf("open(\"%s\")=%d, want -1", string(nonfile), fd)
	}
	var buf [32]byte
	r := Read(-1, unsafe.Pointer(&buf[0]), int32(len(buf)))
	if r != -1 {
		t.Errorf("read()=%d, want -1", r)
	}
	w := Write(^uintptr(0), unsafe.Pointer(&buf[0]), int32(len(buf)))
	if w != -1 {
		t.Errorf("write()=%d, want -1", w)
	}
	c := Close(-1)
	if c != -1 {
		t.Errorf("close()=%d, want -1", c)
	}
}

func TestAppendGrowth(t *testing.T) {
	var x []int64
	check := func(want int) {
		if cap(x) != want {
			t.Errorf("len=%d, cap=%d, want cap=%d", len(x), cap(x), want)
		}
	}

	check(0)
	want := 1
	for i := 1; i <= 100; i++ {
		x = append(x, 1)
		check(want)
		if i&(i-1) == 0 {
			want = 2 * i
		}
	}
}

var One = []int64{1}

func TestAppendSliceGrowth(t *testing.T) {
	var x []int64
	check := func(want int) {
		if cap(x) != want {
			t.Errorf("len=%d, cap=%d, want cap=%d", len(x), cap(x), want)
		}
	}

	check(0)
	want := 1
	for i := 1; i <= 100; i++ {
		x = append(x, One...)
		check(want)
		if i&(i-1) == 0 {
			want = 2 * i
		}
	}
}

func TestGoroutineProfileTrivial(t *testing.T) {
	// Calling GoroutineProfile twice in a row should find the same number of goroutines,
	// but it's possible there are goroutines just about to exit, so we might end up
	// with fewer in the second call. Try a few times; it should converge once those
	// zombies are gone.
	for i := 0; ; i++ {
		n1, ok := GoroutineProfile(nil) // should fail, there's at least 1 goroutine
		if n1 < 1 || ok {
			t.Fatalf("GoroutineProfile(nil) = %d, %v, want >0, false", n1, ok)
		}
		n2, ok := GoroutineProfile(make([]StackRecord, n1))
		if n2 == n1 && ok {
			break
		}
		t.Logf("GoroutineProfile(%d) = %d, %v, want %d, true", n1, n2, ok, n1)
		if i >= 10 {
			t.Fatalf("GoroutineProfile not converging")
		}
	}
}

func TestVersion(t *testing.T) {
	// Test that version does not contain \r or \n.
	vers := Version()
	if strings.Contains(vers, "\r") || strings.Contains(vers, "\n") {
		t.Fatalf("cr/nl in version: %q", vers)
	}
}

// TestIntendedInlining tests that specific runtime functions are inlined.
// This allows refactoring for code clarity and re-use without fear that
// changes to the compiler will cause silent performance regressions.
func TestIntendedInlining(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping in short mode")
	}
	testenv.MustHaveGoRun(t)
	t.Parallel()

	// want is the list of function names that should be inlined.
	want := []string{"tophash", "add", "(*bmap).keys"}

	m := make(map[string]bool, len(want))
	for _, s := range want {
		m[s] = true
	}

	cmd := testEnv(exec.Command(testenv.GoToolPath(t), "build", "-gcflags=-m", "runtime"))
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Logf("%s", out)
		t.Fatal(err)
	}
	lines := bytes.Split(out, []byte{'\n'})
	for _, x := range lines {
		f := bytes.Split(x, []byte(": can inline "))
		if len(f) < 2 {
			continue
		}
		fn := bytes.TrimSpace(f[1])
		delete(m, string(fn))
	}

	for s := range m {
		t.Errorf("function %s not inlined", s)
	}
}
<commit_msg>runtime: always rebuild in TestIntendedInlining<commit_after>// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package runtime_test

import (
	"bytes"
	"internal/testenv"
	"io"
	"os/exec"
	. "runtime"
	"runtime/debug"
	"strings"
	"testing"
	"unsafe"
)

func init() {
	// We're testing the runtime, so make tracebacks show things
	// in the runtime. This only raises the level, so it won't
	// override GOTRACEBACK=crash from the user.
	SetTracebackEnv("system")
}

var errf error

func errfn() error {
	return errf
}

func errfn1() error {
	return io.EOF
}

func BenchmarkIfaceCmp100(b *testing.B) {
	for i := 0; i < b.N; i++ {
		for j := 0; j < 100; j++ {
			if errfn() == io.EOF {
				b.Fatal("bad comparison")
			}
		}
	}
}

func BenchmarkIfaceCmpNil100(b *testing.B) {
	for i := 0; i < b.N; i++ {
		for j := 0; j < 100; j++ {
			if errfn1() == nil {
				b.Fatal("bad comparison")
			}
		}
	}
}

var efaceCmp1 interface{}
var efaceCmp2 interface{}

func BenchmarkEfaceCmpDiff(b *testing.B) {
	x := 5
	efaceCmp1 = &x
	y := 6
	efaceCmp2 = &y
	for i := 0; i < b.N; i++ {
		for j := 0; j < 100; j++ {
			if efaceCmp1 == efaceCmp2 {
				b.Fatal("bad comparison")
			}
		}
	}
}

func BenchmarkDefer(b *testing.B) {
	for i := 0; i < b.N; i++ {
		defer1()
	}
}

func defer1() {
	defer func(x, y, z int) {
		if recover() != nil || x != 1 || y != 2 || z != 3 {
			panic("bad recover")
		}
	}(1, 2, 3)
}

func BenchmarkDefer10(b *testing.B) {
	for i := 0; i < b.N/10; i++ {
		defer2()
	}
}

func defer2() {
	for i := 0; i < 10; i++ {
		defer func(x, y, z int) {
			if recover() != nil || x != 1 || y != 2 || z != 3 {
				panic("bad recover")
			}
		}(1, 2, 3)
	}
}

func BenchmarkDeferMany(b *testing.B) {
	for i := 0; i < b.N; i++ {
		defer func(x, y, z int) {
			if recover() != nil || x != 1 || y != 2 || z != 3 {
				panic("bad recover")
			}
		}(1, 2, 3)
	}
}

// golang.org/issue/7063
func TestStopCPUProfilingWithProfilerOff(t *testing.T) {
	SetCPUProfileRate(0)
}

// Addresses to test for faulting behavior.
// This is less a test of SetPanicOnFault and more a check that
// the operating system and the runtime can process these faults
// correctly. That is, we're indirectly testing that without SetPanicOnFault
// these would manage to turn into ordinary crashes.
// Note that these are truncated on 32-bit systems, so the bottom 32 bits
// of the larger addresses must themselves be invalid addresses.
// We might get unlucky and the OS might have mapped one of these
// addresses, but probably not: they're all in the first page, very high
// addresses that normally an OS would reserve for itself, or malformed
// addresses. Even so, we might have to remove one or two on different
// systems. We will see.

var faultAddrs = []uint64{
	// low addresses
	0,
	1,
	0xfff,
	// high (kernel) addresses
	// or else malformed.
	0xffffffffffffffff,
	0xfffffffffffff001,
	0xffffffffffff0001,
	0xfffffffffff00001,
	0xffffffffff000001,
	0xfffffffff0000001,
	0xffffffff00000001,
	0xfffffff000000001,
	0xffffff0000000001,
	0xfffff00000000001,
	0xffff000000000001,
	0xfff0000000000001,
	0xff00000000000001,
	0xf000000000000001,
	0x8000000000000001,
}

func TestSetPanicOnFault(t *testing.T) {
	old := debug.SetPanicOnFault(true)
	defer debug.SetPanicOnFault(old)

	nfault := 0
	for _, addr := range faultAddrs {
		testSetPanicOnFault(t, uintptr(addr), &nfault)
	}
	if nfault == 0 {
		t.Fatalf("none of the addresses faulted")
	}
}

func testSetPanicOnFault(t *testing.T, addr uintptr, nfault *int) {
	if GOOS == "nacl" {
		t.Skip("nacl doesn't seem to fault on high addresses")
	}

	defer func() {
		if err := recover(); err != nil {
			*nfault++
		}
	}()

	// The read should fault, except that sometimes we hit
	// addresses that have had C or kernel pages mapped there
	// readable by user code. So just log the content.
	// If no addresses fault, we'll fail the test.
	v := *(*byte)(unsafe.Pointer(addr))
	t.Logf("addr %#x: %#x\n", addr, v)
}

func eqstring_generic(s1, s2 string) bool {
	if len(s1) != len(s2) {
		return false
	}
	// optimization in assembly versions:
	// if s1.str == s2.str { return true }
	for i := 0; i < len(s1); i++ {
		if s1[i] != s2[i] {
			return false
		}
	}
	return true
}

func TestEqString(t *testing.T) {
	// This isn't really an exhaustive test of == on strings, it's
	// just a convenient way of documenting (via eqstring_generic)
	// what == does.
	s := []string{
		"",
		"a",
		"c",
		"aaa",
		"ccc",
		"cccc"[:3], // same contents, different string
		"1234567890",
	}
	for _, s1 := range s {
		for _, s2 := range s {
			x := s1 == s2
			y := eqstring_generic(s1, s2)
			if x != y {
				t.Errorf(`("%s" == "%s") = %t, want %t`, s1, s2, x, y)
			}
		}
	}
}

func TestTrailingZero(t *testing.T) {
	// make sure we add padding for structs with trailing zero-sized fields
	type T1 struct {
		n int32
		z [0]byte
	}
	if unsafe.Sizeof(T1{}) != 8 {
		t.Errorf("sizeof(%#v)==%d, want 8", T1{}, unsafe.Sizeof(T1{}))
	}
	type T2 struct {
		n int64
		z struct{}
	}
	if unsafe.Sizeof(T2{}) != 8+unsafe.Sizeof(Uintreg(0)) {
		t.Errorf("sizeof(%#v)==%d, want %d", T2{}, unsafe.Sizeof(T2{}), 8+unsafe.Sizeof(Uintreg(0)))
	}
	type T3 struct {
		n byte
		z [4]struct{}
	}
	if unsafe.Sizeof(T3{}) != 2 {
		t.Errorf("sizeof(%#v)==%d, want 2", T3{}, unsafe.Sizeof(T3{}))
	}
	// make sure padding can double for both zerosize and alignment
	type T4 struct {
		a int32
		b int16
		c int8
		z struct{}
	}
	if unsafe.Sizeof(T4{}) != 8 {
		t.Errorf("sizeof(%#v)==%d, want 8", T4{}, unsafe.Sizeof(T4{}))
	}
	// make sure we don't pad a zero-sized thing
	type T5 struct {
	}
	if unsafe.Sizeof(T5{}) != 0 {
		t.Errorf("sizeof(%#v)==%d, want 0", T5{}, unsafe.Sizeof(T5{}))
	}
}

func TestBadOpen(t *testing.T) {
	if GOOS == "windows" || GOOS == "nacl" {
		t.Skip("skipping OS that doesn't have open/read/write/close")
	}
	// make sure we get the correct error code if open fails. Same for
	// read/write/close on the resulting -1 fd. See issue 10052.
	nonfile := []byte("/notreallyafile")
	fd := Open(&nonfile[0], 0, 0)
	if fd != -1 {
		t.Errorf("open(\"%s\")=%d, want -1", string(nonfile), fd)
	}
	var buf [32]byte
	r := Read(-1, unsafe.Pointer(&buf[0]), int32(len(buf)))
	if r != -1 {
		t.Errorf("read()=%d, want -1", r)
	}
	w := Write(^uintptr(0), unsafe.Pointer(&buf[0]), int32(len(buf)))
	if w != -1 {
		t.Errorf("write()=%d, want -1", w)
	}
	c := Close(-1)
	if c != -1 {
		t.Errorf("close()=%d, want -1", c)
	}
}

func TestAppendGrowth(t *testing.T) {
	var x []int64
	check := func(want int) {
		if cap(x) != want {
			t.Errorf("len=%d, cap=%d, want cap=%d", len(x), cap(x), want)
		}
	}

	check(0)
	want := 1
	for i := 1; i <= 100; i++ {
		x = append(x, 1)
		check(want)
		if i&(i-1) == 0 {
			want = 2 * i
		}
	}
}

var One = []int64{1}

func TestAppendSliceGrowth(t *testing.T) {
	var x []int64
	check := func(want int) {
		if cap(x) != want {
			t.Errorf("len=%d, cap=%d, want cap=%d", len(x), cap(x), want)
		}
	}

	check(0)
	want := 1
	for i := 1; i <= 100; i++ {
		x = append(x, One...)
		check(want)
		if i&(i-1) == 0 {
			want = 2 * i
		}
	}
}

func TestGoroutineProfileTrivial(t *testing.T) {
	// Calling GoroutineProfile twice in a row should find the same number of goroutines,
	// but it's possible there are goroutines just about to exit, so we might end up
	// with fewer in the second call. Try a few times; it should converge once those
	// zombies are gone.
	for i := 0; ; i++ {
		n1, ok := GoroutineProfile(nil) // should fail, there's at least 1 goroutine
		if n1 < 1 || ok {
			t.Fatalf("GoroutineProfile(nil) = %d, %v, want >0, false", n1, ok)
		}
		n2, ok := GoroutineProfile(make([]StackRecord, n1))
		if n2 == n1 && ok {
			break
		}
		t.Logf("GoroutineProfile(%d) = %d, %v, want %d, true", n1, n2, ok, n1)
		if i >= 10 {
			t.Fatalf("GoroutineProfile not converging")
		}
	}
}

func TestVersion(t *testing.T) {
	// Test that version does not contain \r or \n.
	vers := Version()
	if strings.Contains(vers, "\r") || strings.Contains(vers, "\n") {
		t.Fatalf("cr/nl in version: %q", vers)
	}
}

// TestIntendedInlining tests that specific runtime functions are inlined.
// This allows refactoring for code clarity and re-use without fear that
// changes to the compiler will cause silent performance regressions.
func TestIntendedInlining(t *testing.T) {
	if testing.Short() && testenv.Builder() == "" {
		t.Skip("skipping in short mode")
	}
	testenv.MustHaveGoRun(t)
	t.Parallel()

	// want is the list of function names that should be inlined.
	want := []string{"tophash", "add", "(*bmap).keys"}

	m := make(map[string]bool, len(want))
	for _, s := range want {
		m[s] = true
	}

	cmd := testEnv(exec.Command(testenv.GoToolPath(t), "build", "-a", "-gcflags=-m", "runtime"))
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Logf("%s", out)
		t.Fatal(err)
	}
	lines := bytes.Split(out, []byte{'\n'})
	for _, x := range lines {
		f := bytes.Split(x, []byte(": can inline "))
		if len(f) < 2 {
			continue
		}
		fn := bytes.TrimSpace(f[1])
		delete(m, string(fn))
	}

	for s := range m {
		t.Errorf("function %s not inlined", s)
	}
}
<|endoftext|>