<commit_before>/*
Copyright 2019 The logr Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package logr defines abstract interfaces for logging.  Packages can depend on
// these interfaces and callers can implement logging in whatever way is
// appropriate.
//
// This design derives from Dave Cheney's blog:
//     http://dave.cheney.net/2015/11/05/lets-talk-about-logging
//
// This is a BETA grade API.  Until there is a significant 2nd implementation,
// I don't really know how it will change.
//
// The logging specifically makes it non-trivial to use format strings, to encourage
// attaching structured information instead of unstructured format strings.
//
// Usage
//
// Logging is done using a Logger.  Loggers can have name prefixes and named
// values attached, so that all log messages logged with that Logger have some
// base context associated.
//
// The term "key" is used to refer to the name associated with a particular
// value, to disambiguate it from the general Logger name.
//
// For instance, suppose we're trying to reconcile the state of an object, and
// we want to log that we've made some decision.
//
// With the traditional log package, we might write:
//   log.Printf("decided to set field foo to value %q for object %s/%s",
//       targetValue, object.Namespace, object.Name)
//
// With logr's structured logging, we'd write:
//   // elsewhere in the file, set up the logger to log with the prefix of
//   // "reconcilers", and the named value target-type=Foo, for extra context.
//   log := mainLogger.WithName("reconcilers").WithValues("target-type", "Foo")
//
//   // later on...
//   log.Info("setting foo on object", "value", targetValue, "object", object)
//
// Depending on our logging implementation, we could then make logging decisions
// based on field values (like only logging such events for objects in a certain
// namespace), or copy the structured information into a structured log store.
//
// For logging errors, Logger has a method called Error.  Suppose we wanted to
// log an error while reconciling.  With the traditional log package, we might
// write:
//   log.Errorf("unable to reconcile object %s/%s: %v", object.Namespace, object.Name, err)
//
// With logr, we'd instead write:
//   // assuming the above setup for log
//   log.Error(err, "unable to reconcile object", "object", object)
//
// This functions similarly to:
//   log.Info("unable to reconcile object", "error", err, "object", object)
//
// However, it ensures that a standard key for the error value ("error") is used
// across all error logging.  Furthermore, certain implementations may choose to
// attach additional information (such as stack traces) on calls to Error, so
// it's preferred to use Error to log errors.
//
// Parts of a log line
//
// Each log message from a Logger has four types of context:
// logger name, log verbosity, log message, and the named values.
//
// The Logger name consists of a series of name "segments" added by successive
// calls to WithName.  These name segments will be joined in some way by the
// underlying implementation.  It is strongly recommended that name segments
// contain simple identifiers (letters, digits, and hyphen), and do not contain
// characters that could muddle the log output or confuse the joining operation
// (e.g.  whitespace, commas, periods, slashes, brackets, quotes, etc).
//
// Log verbosity represents how little a log matters.  Level zero, the default,
// matters most.  Increasing levels matter less and less.  Try to avoid lots of
// different verbosity levels, and instead provide useful keys, logger names,
// and log messages for users to filter on.  It's illegal to pass a log level
// below zero.
//
// The log message consists of a constant message attached to the log line.
// This should generally be a simple description of what's occurring, and should
// never be a format string.
//
// Variable information can then be attached using named values (key/value
// pairs).  Keys are arbitrary strings, while values may be any Go value.
//
// Key Naming Conventions
//
// Keys are not strictly required to conform to any specification or regex, but
// it is recommended that they:
//   * be human-readable and meaningful (not auto-generated or simple ordinals)
//   * be constant (not dependent on input data)
//   * contain only printable characters
//   * not contain whitespace or punctuation
//
// These guidelines help ensure that log data is processed properly regardless
// of the log implementation.  For example, log implementations will try to
// output JSON data or will store data for later database (e.g. SQL) queries.
//
// While users are generally free to use key names of their choice, it's
// generally best to avoid using the following keys, as they're frequently used
// by implementations:
//
// - `"caller"`: the calling information (file/line) of a particular log line.
// - `"error"`: the underlying error value in the `Error` method.
// - `"level"`: the log level.
// - `"logger"`: the name of the associated logger.
// - `"msg"`: the log message.
// - `"stacktrace"`: the stack trace associated with a particular log line or
//                   error (often from the `Error` message).
// - `"ts"`: the timestamp for a log line.
//
// Implementations are encouraged to make use of these keys to represent the
// above concepts, when necessary (for example, in a pure-JSON output form, it
// would be necessary to represent at least message and timestamp as ordinary
// named values).
//
// Implementations may choose to give callers access to the underlying
// logging implementation.  The recommended pattern for this is:
//   // Underlier exposes access to the underlying logging implementation.
//   // Since callers only have a logr.Logger, they have to know which
//   // implementation is in use, so this interface is less of an abstraction
//   // and more of way to test type conversion.
//   type Underlier interface {
//       GetUnderlying() <underlying-type>
//   }
package logr

import (
	"context"
)

// TODO: consider adding back in format strings if they're really needed
// TODO: consider other bits of zap/zapcore functionality like ObjectMarshaller (for arbitrary objects)
// TODO: consider other bits of glog functionality like Flush, InfoDepth, OutputStats

// Logger represents the ability to log messages, both errors and not.
type Logger interface {
	// Enabled tests whether this Logger is enabled.  For example, commandline
	// flags might be used to set the logging verbosity and disable some info
	// logs.
	Enabled() bool

	// Info logs a non-error message with the given key/value pairs as context.
	//
	// The msg argument should be used to add some constant description to
	// the log line.  The key/value pairs can then be used to add additional
	// variable information.  The key/value pairs should alternate string
	// keys and arbitrary values.
	Info(msg string, keysAndValues ...interface{})

	// Error logs an error, with the given message and key/value pairs as context.
	// It functions similarly to calling Info with the "error" named value, but may
	// have unique behavior, and should be preferred for logging errors (see the
	// package documentations for more information).
	//
	// The msg field should be used to add context to any underlying error,
	// while the err field should be used to attach the actual error that
	// triggered this log line, if present.
	Error(err error, msg string, keysAndValues ...interface{})

	// V returns an Logger value for a specific verbosity level, relative to
	// this Logger.  In other words, V values are additive.  V higher verbosity
	// level means a log message is less important.  It's illegal to pass a log
	// level less than zero.
	V(level int) Logger

	// WithValues adds some key-value pairs of context to a logger.
	// See Info for documentation on how key/value pairs work.
	WithValues(keysAndValues ...interface{}) Logger

	// WithName adds a new element to the logger's name.
	// Successive calls with WithName continue to append
	// suffixes to the logger's name.  It's strongly recommended
	// that name segments contain only letters, digits, and hyphens
	// (see the package documentation for more information).
	WithName(name string) Logger
}

// InfoLogger provides compatibility with code that relies on the v0.1.0 interface
// Deprecated: use Logger instead. This will be removed in a future release.
type InfoLogger = Logger

type contextKey struct{}

// FromContext returns a Logger constructed from ctx or nil if no
// logger details are found.
func FromContext(ctx context.Context) Logger {
	if v, ok := ctx.Value(contextKey{}).(Logger); ok {
		return v
	}

	return nil
}

// FromContextOrDiscard returns a Logger constructed from ctx or a Logger
// that discards all messages if no logger details are found.
func FromContextOrDiscard(ctx context.Context) Logger {
	if v, ok := ctx.Value(contextKey{}).(Logger); ok {
		return v
	}

	return discardLogger{}
}

// NewContext returns a new context derived from ctx that embeds the Logger.
func NewContext(ctx context.Context, l Logger) context.Context {
	return context.WithValue(ctx, contextKey{}, l)
}
<commit_msg>Move comments arount for cleaner godoc<commit_after>/*
Copyright 2019 The logr Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// This design derives from Dave Cheney's blog:
//     http://dave.cheney.net/2015/11/05/lets-talk-about-logging
//
// This is a BETA grade API.  Until there is a significant 2nd implementation,
// I don't really know how it will change.

// Package logr defines abstract interfaces for logging.  Packages can depend on
// these interfaces and callers can implement logging in whatever way is
// appropriate.
//
// Usage
//
// Logging is done using a Logger.  Loggers can have name prefixes and named
// values attached, so that all log messages logged with that Logger have some
// base context associated.
//
// The term "key" is used to refer to the name associated with a particular
// value, to disambiguate it from the general Logger name.
//
// For instance, suppose we're trying to reconcile the state of an object, and
// we want to log that we've made some decision.
//
// With the traditional log package, we might write:
//   log.Printf("decided to set field foo to value %q for object %s/%s",
//       targetValue, object.Namespace, object.Name)
//
// With logr's structured logging, we'd write:
//   // elsewhere in the file, set up the logger to log with the prefix of
//   // "reconcilers", and the named value target-type=Foo, for extra context.
//   log := mainLogger.WithName("reconcilers").WithValues("target-type", "Foo")
//
//   // later on...
//   log.Info("setting foo on object", "value", targetValue, "object", object)
//
// Depending on our logging implementation, we could then make logging decisions
// based on field values (like only logging such events for objects in a certain
// namespace), or copy the structured information into a structured log store.
//
// For logging errors, Logger has a method called Error.  Suppose we wanted to
// log an error while reconciling.  With the traditional log package, we might
// write:
//   log.Errorf("unable to reconcile object %s/%s: %v", object.Namespace, object.Name, err)
//
// With logr, we'd instead write:
//   // assuming the above setup for log
//   log.Error(err, "unable to reconcile object", "object", object)
//
// This functions similarly to:
//   log.Info("unable to reconcile object", "error", err, "object", object)
//
// However, it ensures that a standard key for the error value ("error") is used
// across all error logging.  Furthermore, certain implementations may choose to
// attach additional information (such as stack traces) on calls to Error, so
// it's preferred to use Error to log errors.
//
// Parts of a log line
//
// Each log message from a Logger has four types of context:
// logger name, log verbosity, log message, and the named values.
//
// The Logger name consists of a series of name "segments" added by successive
// calls to WithName.  These name segments will be joined in some way by the
// underlying implementation.  It is strongly recommended that name segments
// contain simple identifiers (letters, digits, and hyphen), and do not contain
// characters that could muddle the log output or confuse the joining operation
// (e.g.  whitespace, commas, periods, slashes, brackets, quotes, etc).
//
// Log verbosity represents how little a log matters.  Level zero, the default,
// matters most.  Increasing levels matter less and less.  Try to avoid lots of
// different verbosity levels, and instead provide useful keys, logger names,
// and log messages for users to filter on.  It's illegal to pass a log level
// below zero.
//
// The log message consists of a constant message attached to the log line.
// This should generally be a simple description of what's occurring, and should
// never be a format string.
//
// Variable information can then be attached using named values (key/value
// pairs).  Keys are arbitrary strings, while values may be any Go value.
//
// Key Naming Conventions
//
// Keys are not strictly required to conform to any specification or regex, but
// it is recommended that they:
//   * be human-readable and meaningful (not auto-generated or simple ordinals)
//   * be constant (not dependent on input data)
//   * contain only printable characters
//   * not contain whitespace or punctuation
//
// These guidelines help ensure that log data is processed properly regardless
// of the log implementation.  For example, log implementations will try to
// output JSON data or will store data for later database (e.g. SQL) queries.
//
// While users are generally free to use key names of their choice, it's
// generally best to avoid using the following keys, as they're frequently used
// by implementations:
//
//   * `"caller"`: the calling information (file/line) of a particular log line.
//   * `"error"`: the underlying error value in the `Error` method.
//   * `"level"`: the log level.
//   * `"logger"`: the name of the associated logger.
//   * `"msg"`: the log message.
//   * `"stacktrace"`: the stack trace associated with a particular log line or
//                     error (often from the `Error` message).
//   * `"ts"`: the timestamp for a log line.
//
// Implementations are encouraged to make use of these keys to represent the
// above concepts, when necessary (for example, in a pure-JSON output form, it
// would be necessary to represent at least message and timestamp as ordinary
// named values).
//
// Implementations may choose to give callers access to the underlying
// logging implementation.  The recommended pattern for this is:
//   // Underlier exposes access to the underlying logging implementation.
//   // Since callers only have a logr.Logger, they have to know which
//   // implementation is in use, so this interface is less of an abstraction
//   // and more of way to test type conversion.
//   type Underlier interface {
//       GetUnderlying() <underlying-type>
//   }
package logr

import (
	"context"
)

// TODO: consider adding back in format strings if they're really needed
// TODO: consider other bits of zap/zapcore functionality like ObjectMarshaller (for arbitrary objects)
// TODO: consider other bits of glog functionality like Flush, InfoDepth, OutputStats

// Logger represents the ability to log messages, both errors and not.
type Logger interface {
	// Enabled tests whether this Logger is enabled.  For example, commandline
	// flags might be used to set the logging verbosity and disable some info
	// logs.
	Enabled() bool

	// Info logs a non-error message with the given key/value pairs as context.
	//
	// The msg argument should be used to add some constant description to
	// the log line.  The key/value pairs can then be used to add additional
	// variable information.  The key/value pairs should alternate string
	// keys and arbitrary values.
	Info(msg string, keysAndValues ...interface{})

	// Error logs an error, with the given message and key/value pairs as context.
	// It functions similarly to calling Info with the "error" named value, but may
	// have unique behavior, and should be preferred for logging errors (see the
	// package documentations for more information).
	//
	// The msg field should be used to add context to any underlying error,
	// while the err field should be used to attach the actual error that
	// triggered this log line, if present.
	Error(err error, msg string, keysAndValues ...interface{})

	// V returns an Logger value for a specific verbosity level, relative to
	// this Logger.  In other words, V values are additive.  V higher verbosity
	// level means a log message is less important.  It's illegal to pass a log
	// level less than zero.
	V(level int) Logger

	// WithValues adds some key-value pairs of context to a logger.
	// See Info for documentation on how key/value pairs work.
	WithValues(keysAndValues ...interface{}) Logger

	// WithName adds a new element to the logger's name.
	// Successive calls with WithName continue to append
	// suffixes to the logger's name.  It's strongly recommended
	// that name segments contain only letters, digits, and hyphens
	// (see the package documentation for more information).
	WithName(name string) Logger
}

// InfoLogger provides compatibility with code that relies on the v0.1.0
// interface.
//
// Deprecated: use Logger instead. This will be removed in a future release.
type InfoLogger = Logger

type contextKey struct{}

// FromContext returns a Logger constructed from ctx or nil if no
// logger details are found.
func FromContext(ctx context.Context) Logger {
	if v, ok := ctx.Value(contextKey{}).(Logger); ok {
		return v
	}

	return nil
}

// FromContextOrDiscard returns a Logger constructed from ctx or a Logger
// that discards all messages if no logger details are found.
func FromContextOrDiscard(ctx context.Context) Logger {
	if v, ok := ctx.Value(contextKey{}).(Logger); ok {
		return v
	}

	return discardLogger{}
}

// NewContext returns a new context derived from ctx that embeds the Logger.
func NewContext(ctx context.Context, l Logger) context.Context {
	return context.WithValue(ctx, contextKey{}, l)
}
<|endoftext|>


<commit_before>/*
Package atexit registers a function to be called at normal process termination (by ^C = SIGINT = "kill -2").

Installation

In the terminal:

	$ go get github.com/whitedevops/atexit
*/
package atexit

import (
	"os"
	"os/signal"
)

var s chan os.Signal

// Use registers the atexit function.
// It can be called only once per program.
func Use(f func()) {
	if s != nil {
		panic("atexit can only be called a single time")
	}

	s = make(chan os.Signal, 1)
	signal.Notify(s, os.Interrupt)

	go func() {
		<-s
		f()
		os.Exit(0)
	}()
}
<commit_msg>Add usage to GoDoc<commit_after>/*
Package atexit registers a function to be called at normal process termination (by ^C = SIGINT = "kill -2").

Installation

In the terminal:

	$ go get github.com/whitedevops/atexit

Usage

Example:

	package main

	import (
		"github.com/whitedevops/atexit"
	)

	func main() {
		atexit.Use(func() {
			// Do something before exit…
		})
	}
*/
package atexit

import (
	"os"
	"os/signal"
)

var s chan os.Signal

// Use registers the atexit function.
// It can be called only once per program.
func Use(f func()) {
	if s != nil {
		panic("atexit can only be called a single time")
	}

	s = make(chan os.Signal, 1)
	signal.Notify(s, os.Interrupt)

	go func() {
		<-s
		f()
		os.Exit(0)
	}()
}
<|endoftext|>


<commit_before>/* Copyright (C) 2015-2020 김운하 (unha.kim@ghts.org)

이 파일은 GHTS의 일부입니다.

이 프로그램은 자유 소프트웨어입니다.
소프트웨어의 피양도자는 자유 소프트웨어 재단이 공표한 GNU LGPL 2.1판
규정에 따라 프로그램을 개작하거나 재배포할 수 있습니다.

이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만,
특정한 목적에 적합하다거나, 이익을 안겨줄 수 있다는 묵시적인 보증을 포함한
어떠한 형태의 보증도 제공하지 않습니다.
보다 자세한 사항에 대해서는 GNU LGPL 2.1판을 참고하시기 바랍니다.
GNU LGPL 2.1판은 이 프로그램과 함께 제공됩니다.
만약, 이 문서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다.
(자유 소프트웨어 재단 : Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA)

Copyright (C) 2015-2020년 UnHa Kim (unha.kim@ghts.org)

This file is part of GHTS.

GHTS is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, version 2.1 of the License.

GHTS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with GHTS.  If not, see <http://www.gnu.org/licenses/>. */

package xing

import (
	"github.com/ghts/ghts/lib"
	krx "github.com/ghts/ghts/lib/krx_time"
	"github.com/ghts/ghts/lib/nanomsg"
	xt "github.com/ghts/ghts/xing/base"
	"testing"
)

func TestF체결_실시간_정보(t *testing.T) {
	t.Parallel()

	if !krx.F한국증시_정규_거래_시간임() {
		t.SkipNow()
	}

	const 종목코드_코스피 = "005930" // 삼성전자
	const 종목코드_코스닥 = "091990" // 셀트리온 헬스케어
	const 종목코드_ETF = "069500" // KODEX 200

	종목_코스피, 에러 := F종목by코드(종목코드_코스피)
	lib.F테스트_에러없음(t, 에러)
	lib.F테스트_같음(t, 종목_코스피.G시장구분(), lib.P시장구분_코스피)

	종목_코스닥, 에러 := F종목by코드(종목코드_코스닥)
	lib.F테스트_에러없음(t, 에러)
	lib.F테스트_같음(t, 종목_코스닥.G시장구분(), lib.P시장구분_코스닥)

	종목_ETF, 에러 := F종목by코드(종목코드_ETF)
	lib.F테스트_에러없음(t, 에러)
	lib.F테스트_같음(t, 종목_ETF.G시장구분(), lib.P시장구분_ETF)

	소켓SUB_실시간 := nano.NewNano소켓SUB_단순형(xt.F주소_실시간())

	lib.F테스트_에러없음(t, F체결_실시간_정보_구독(종목코드_코스피))
	lib.F테스트_에러없음(t, F체결_실시간_정보_구독(종목코드_코스닥))
	lib.F테스트_에러없음(t, F체결_실시간_정보_구독(종목코드_ETF))

	defer func() {
		lib.F테스트_에러없음(t, F체결_실시간_정보_해지(종목코드_코스피))
		lib.F테스트_에러없음(t, F체결_실시간_정보_해지(종목코드_코스닥))
		lib.F테스트_에러없음(t, F체결_실시간_정보_해지(종목코드_ETF))
	}()

	var 코스피_수신, 코스닥_수신, ETF_수신 bool

	// 실시간 정보 수신 확인
	for i := 0; i < 1000; i++ {
		바이트_변환_모음, 에러 := 소켓SUB_실시간.G수신()
		lib.F테스트_에러없음(t, 에러)

		i실시간_정보 := 바이트_변환_모음.S해석기(xt.F바이트_변환값_해석).G해석값_단순형(0)

		체결_실시간_정보, ok := i실시간_정보.(*xt.S체결)
		if !ok {
			continue
		}

		switch 체결_실시간_정보.M종목코드 {
		case 종목코드_코스피:
			코스피_수신 = true
			lib.F테스트_에러없음(t, F체결_실시간_정보_해지(종목코드_코스피))
		case 종목코드_코스닥:
			코스닥_수신 = true
			lib.F테스트_에러없음(t, F체결_실시간_정보_해지(종목코드_코스닥))
		case 종목코드_ETF:
			ETF_수신 = true
			lib.F테스트_에러없음(t, F체결_실시간_정보_해지(종목코드_ETF))
		}

		if 코스피_수신 && 코스닥_수신 && ETF_수신 {
			return
		}
	}

	t.FailNow()
}
<commit_msg>체결 실시간 정보 테스트 케이스 버그 수정.<commit_after>/* Copyright (C) 2015-2020 김운하 (unha.kim@ghts.org)

이 파일은 GHTS의 일부입니다.

이 프로그램은 자유 소프트웨어입니다.
소프트웨어의 피양도자는 자유 소프트웨어 재단이 공표한 GNU LGPL 2.1판
규정에 따라 프로그램을 개작하거나 재배포할 수 있습니다.

이 프로그램은 유용하게 사용될 수 있으리라는 희망에서 배포되고 있지만,
특정한 목적에 적합하다거나, 이익을 안겨줄 수 있다는 묵시적인 보증을 포함한
어떠한 형태의 보증도 제공하지 않습니다.
보다 자세한 사항에 대해서는 GNU LGPL 2.1판을 참고하시기 바랍니다.
GNU LGPL 2.1판은 이 프로그램과 함께 제공됩니다.
만약, 이 문서가 누락되어 있다면 자유 소프트웨어 재단으로 문의하시기 바랍니다.
(자유 소프트웨어 재단 : Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA)

Copyright (C) 2015-2020년 UnHa Kim (unha.kim@ghts.org)

This file is part of GHTS.

GHTS is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, version 2.1 of the License.

GHTS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with GHTS.  If not, see <http://www.gnu.org/licenses/>. */

package xing

import (
	"github.com/ghts/ghts/lib"
	krx "github.com/ghts/ghts/lib/krx_time"
	"github.com/ghts/ghts/lib/nanomsg"
	xt "github.com/ghts/ghts/xing/base"
	"testing"
)

func TestF체결_실시간_정보(t *testing.T) {
	t.Parallel()

	if !krx.F한국증시_정규_거래_시간임() {
		t.SkipNow()
	}

	const 종목코드_코스피 = "005930" // 삼성전자
	const 종목코드_코스닥 = "091990" // 셀트리온 헬스케어
	const 종목코드_ETF = "069500" // KODEX 200

	종목_코스피, 에러 := F종목by코드(종목코드_코스피)
	lib.F테스트_에러없음(t, 에러)
	lib.F테스트_같음(t, 종목_코스피.G시장구분(), lib.P시장구분_코스피)

	종목_코스닥, 에러 := F종목by코드(종목코드_코스닥)
	lib.F테스트_에러없음(t, 에러)
	lib.F테스트_같음(t, 종목_코스닥.G시장구분(), lib.P시장구분_코스닥)

	종목_ETF, 에러 := F종목by코드(종목코드_ETF)
	lib.F테스트_에러없음(t, 에러)
	lib.F테스트_같음(t, 종목_ETF.G시장구분(), lib.P시장구분_ETF)

	소켓SUB_실시간 := nano.NewNano소켓SUB_단순형(xt.F주소_실시간())

	lib.F테스트_에러없음(t, F체결_실시간_정보_구독(종목코드_코스피))
	lib.F테스트_에러없음(t, F체결_실시간_정보_구독(종목코드_코스닥))
	lib.F테스트_에러없음(t, F체결_실시간_정보_구독(종목코드_ETF))

	defer func() {
		lib.F테스트_에러없음(t, F체결_실시간_정보_해지(종목코드_코스피))
		lib.F테스트_에러없음(t, F체결_실시간_정보_해지(종목코드_코스닥))
		lib.F테스트_에러없음(t, F체결_실시간_정보_해지(종목코드_ETF))
	}()

	var 코스피_수신, 코스닥_수신, ETF_수신 bool

	// 실시간 정보 수신 확인
	for i := 0; i < 1000; i++ {
		바이트_변환_모음, 에러 := 소켓SUB_실시간.G수신()
		lib.F테스트_에러없음(t, 에러)

		i실시간_정보 := 바이트_변환_모음.S해석기(xt.F바이트_변환값_해석).G해석값_단순형(0)

		종목코드 := ""

		switch 값 := i실시간_정보.(type) {
		case *xt.S코스피_체결:
			종목코드 = 값.M종목코드
		case *xt.S코스닥_체결:
			종목코드 = 값.M종목코드
		}

		switch 종목코드 {
		case 종목코드_코스피:
			코스피_수신 = true
			lib.F테스트_에러없음(t, F체결_실시간_정보_해지(종목코드_코스피))
		case 종목코드_코스닥:
			코스닥_수신 = true
			lib.F테스트_에러없음(t, F체결_실시간_정보_해지(종목코드_코스닥))
		case 종목코드_ETF:
			ETF_수신 = true
			lib.F테스트_에러없음(t, F체결_실시간_정보_해지(종목코드_ETF))
		}

		if 코스피_수신 && 코스닥_수신 && ETF_수신 {
			return
		}
	}

	t.FailNow()
}
<|endoftext|>


<commit_before>package ss
// Is ss too short?
// Oh well.. http://golang.org/doc/effective_go.html#package-names
// Alias it if it bugs you

import (
  "C"
  "errors"
  "fmt"
  "net"
  "os"
  "strconv"
  "strings"
)

type ListenTarget struct {
  Name  string // host:port | port. Currently unix sockets are not supported
  Fd    uintptr
}

func Ports() ([]ListenTarget, error) {
  ssport := os.Getenv("SERVER_STARTER_PORT")
  if ssport == "" {
    return nil, errors.New("No environment variable SERVER_STARTER_PORT available")
  }

  return ParsePorts(ssport)
}

func ParsePorts(ssport string) ([]ListenTarget, error) {
  ret := []ListenTarget{}
  for _, pairstring := range strings.Split(ssport, ";") {
    pair := strings.Split(pairstring, "=")
    port, err := strconv.ParseUint(pair[1], 10, 0)
    if err != nil {
      return nil, errors.New(fmt.Sprintf("Failed to parse '%s'", pairstring))
    }
    ret = append(ret, ListenTarget { pair[0], uintptr(port) })
  }
  return ret, nil
}

func NewListener() (net.Listener, error) {
  portmap, err := Ports()
  if err != nil {
    return nil, err
  }
  return ListenOn(portmap[0])
}

func ListenersIn (list []ListenTarget) ([]net.Listener, error) {
  ret := []net.Listener {}
  for _, t := range list {
    l, err := ListenOn(t)
    if err != nil {
      return nil, err
    }
    ret = append(ret, l)
  }
  return ret, nil
}

func ListenOn (t ListenTarget) (net.Listener, error) {
  f := os.NewFile(t.Fd, "foo")
  return net.FileListener(f)
}
<commit_msg>oops, artifact from the past slipped in..<commit_after>package ss
// Is ss too short?
// Oh well.. http://golang.org/doc/effective_go.html#package-names
// Alias it if it bugs you

import (
  "errors"
  "fmt"
  "net"
  "os"
  "strconv"
  "strings"
)

type ListenTarget struct {
  Name  string // host:port | port. Currently unix sockets are not supported
  Fd    uintptr
}

func Ports() ([]ListenTarget, error) {
  ssport := os.Getenv("SERVER_STARTER_PORT")
  if ssport == "" {
    return nil, errors.New("No environment variable SERVER_STARTER_PORT available")
  }

  return ParsePorts(ssport)
}

func ParsePorts(ssport string) ([]ListenTarget, error) {
  ret := []ListenTarget{}
  for _, pairstring := range strings.Split(ssport, ";") {
    pair := strings.Split(pairstring, "=")
    port, err := strconv.ParseUint(pair[1], 10, 0)
    if err != nil {
      return nil, errors.New(fmt.Sprintf("Failed to parse '%s'", pairstring))
    }
    ret = append(ret, ListenTarget { pair[0], uintptr(port) })
  }
  return ret, nil
}

func NewListener() (net.Listener, error) {
  portmap, err := Ports()
  if err != nil {
    return nil, err
  }
  return ListenOn(portmap[0])
}

func ListenersIn (list []ListenTarget) ([]net.Listener, error) {
  ret := []net.Listener {}
  for _, t := range list {
    l, err := ListenOn(t)
    if err != nil {
      return nil, err
    }
    ret = append(ret, l)
  }
  return ret, nil
}

func ListenOn (t ListenTarget) (net.Listener, error) {
  f := os.NewFile(t.Fd, "foo")
  return net.FileListener(f)
}
<|endoftext|>


<commit_before>package ss

import (
  "errors"
  "fmt"
  "net"
  "os"
  "strconv"
  "strings"
)

type ListenTarget struct {
  Name  string // host:port | port. Currently unix sockets are not supported
  Fd    uintptr
}

/*

Parses SERVER_STARTER_PORT environment variable, and returns a list of
of ListenTarget structs that can be passed to NewListenerOn()

*/
func Ports() ([]ListenTarget, error) {
  ssport := os.Getenv("SERVER_STARTER_PORT")
  if ssport == "" {
    return nil, errors.New("No environment variable SERVER_STARTER_PORT available")
  }

  return ParsePorts(ssport)
}

/*

Parses the given string and returns a list of
of ListenTarget structs that can be passed to NewListenerOn()

*/
func ParsePorts(ssport string) ([]ListenTarget, error) {
  ret := []ListenTarget{}
  for _, pairstring := range strings.Split(ssport, ";") {
    pair := strings.Split(pairstring, "=")
    port, err := strconv.ParseUint(pair[1], 10, 0)
    if err != nil {
      return nil, errors.New(fmt.Sprintf("Failed to parse '%s'", pairstring))
    }
    ret = append(ret, ListenTarget { pair[0], uintptr(port) })
  }
  return ret, nil
}

/*

Creates a new listener from SERVER_STARTER_PORT, or if that fails, binds
to the "default" bind address

*/
func NewListenerOrDefault(proto, defaultBindAddress string) (net.Listener, error) {
  l, err := NewListener()
  if err != nil {
    dl, err := net.Listen(proto, defaultBindAddress)
    if err != nil {
      return nil, err
    }
    return dl, nil
  }
  return l, nil
}

/*

Creates a new listener from SERVER_STARTER_PORT environment variable

Note that this binds to only ONE file descriptor.
If multiple ports are specified in the environment variable, the first one is used

 */
func NewListener() (net.Listener, error) {
  portmap, err := Ports()
  if err != nil {
    return nil, err
  }
  return NewListenerOn(portmap[0])
}

/*

Creates new listeners from SERVER_STARTER_PORT environment variable.

This binds to ALL file descriptors in SERVER_STARTER_PORT

*/
func AllListeners() ([]net.Listener, error) {
  portmap, err := Ports()
  if err != nil {
    return nil, err
  }
  return NewListenersOn(portmap)
}

/*

Given a list of ListenTargets, creates listeners for each one

*/
func NewListenersOn (list []ListenTarget) ([]net.Listener, error) {
  ret := []net.Listener {}
  for _, t := range list {
    l, err := NewListenerOn(t)
    if err != nil {
      return nil, err
    }
    ret = append(ret, l)
  }
  return ret, nil
}

/*

Given a ListenTarget, creates a listener

*/
func NewListenerOn (t ListenTarget) (net.Listener, error) {
  f := os.NewFile(t.Fd, t.Name)
  return net.FileListener(f)
}
<commit_msg>slight refactor<commit_after>package ss

import (
  "errors"
  "fmt"
  "net"
  "os"
  "strconv"
  "strings"
)

type ListenTarget struct {
  Name  string // host:port | port. Currently unix sockets are not supported
  Fd    uintptr
}

/*

Parses SERVER_STARTER_PORT environment variable, and returns a list of
of ListenTarget structs that can be passed to NewListenerOn()

*/
func Ports() ([]ListenTarget, error) {
  ssport := os.Getenv("SERVER_STARTER_PORT")
  if ssport == "" {
    return nil, errors.New("No environment variable SERVER_STARTER_PORT available")
  }

  return ParsePorts(ssport)
}

/*

Parses the given string and returns a list of
of ListenTarget structs that can be passed to NewListenerOn()

*/
func ParsePorts(ssport string) ([]ListenTarget, error) {
  ret := []ListenTarget{}
  for _, pairstring := range strings.Split(ssport, ";") {
    pair := strings.Split(pairstring, "=")
    port, err := strconv.ParseUint(pair[1], 10, 0)
    if err != nil {
      return nil, errors.New(fmt.Sprintf("Failed to parse '%s'", pairstring))
    }
    ret = append(ret, ListenTarget { pair[0], uintptr(port) })
  }
  return ret, nil
}

/*

Creates a new listener from SERVER_STARTER_PORT, or if that fails, binds
to the "default" bind address

*/
func NewListenerOrDefault(proto, defaultBindAddress string) (net.Listener, error) {
  l, err := NewListener()
  if err == nil {
    return l, nil
  }

  dl, err := net.Listen(proto, defaultBindAddress)
  if err == nil {
    return dl, nil
  }
  return nil, err
}

/*

Creates a new listener from SERVER_STARTER_PORT environment variable

Note that this binds to only ONE file descriptor.
If multiple ports are specified in the environment variable, the first one is used

 */
func NewListener() (net.Listener, error) {
  portmap, err := Ports()
  if err != nil {
    return nil, err
  }
  return NewListenerOn(portmap[0])
}

/*

Creates new listeners from SERVER_STARTER_PORT environment variable.

This binds to ALL file descriptors in SERVER_STARTER_PORT

*/
func AllListeners() ([]net.Listener, error) {
  portmap, err := Ports()
  if err != nil {
    return nil, err
  }
  return NewListenersOn(portmap)
}

/*

Given a list of ListenTargets, creates listeners for each one

*/
func NewListenersOn (list []ListenTarget) ([]net.Listener, error) {
  ret := []net.Listener {}
  for _, t := range list {
    l, err := NewListenerOn(t)
    if err != nil {
      return nil, err
    }
    ret = append(ret, l)
  }
  return ret, nil
}

/*

Given a ListenTarget, creates a listener

*/
func NewListenerOn (t ListenTarget) (net.Listener, error) {
  f := os.NewFile(t.Fd, t.Name)
  return net.FileListener(f)
}
<|endoftext|>


<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor: Julien Vehent jvehent@mozilla.com [:ulfr]

package mig /* import "mig.ninja/mig" */

import (
	"bytes"
	"compress/gzip"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"strconv"
	"sync"
	"time"

	"mig.ninja/mig/pgp"
)

// ActionVersion is the version of the syntax that is expected
const ActionVersion uint16 = 2

// an Action is the json object that is created by an investigator
// and provided to the MIG platform. It must be PGP signed.
type Action struct {
	ID             float64        `json:"id"`
	Name           string         `json:"name"`
	Target         string         `json:"target"`
	Description    Description    `json:"description,omitempty"`
	Threat         Threat         `json:"threat,omitempty"`
	ValidFrom      time.Time      `json:"validfrom"`
	ExpireAfter    time.Time      `json:"expireafter"`
	Operations     []Operation    `json:"operations"`
	PGPSignatures  []string       `json:"pgpsignatures"`
	Investigators  []Investigator `json:"investigators,omitempty"`
	Status         string         `json:"status,omitempty"`
	StartTime      time.Time      `json:"starttime,omitempty"`
	FinishTime     time.Time      `json:"finishtime,omitempty"`
	LastUpdateTime time.Time      `json:"lastupdatetime,omitempty"`
	Counters       ActionCounters `json:"counters,omitempty"`
	SyntaxVersion  uint16         `json:"syntaxversion,omitempty"`
}

// Some counters used to track the completion of an action
type ActionCounters struct {
	Sent      int `json:"sent,omitempty"`
	Done      int `json:"done,omitempty"`
	InFlight  int `json:"inflight,omitempty"`
	Success   int `json:"success,omitempty"`
	Cancelled int `json:"cancelled,omitempty"`
	Expired   int `json:"expired,omitempty"`
	Failed    int `json:"failed,omitempty"`
	TimeOut   int `json:"timeout,omitempty"`
}

// a description is a simple object that contains detail about the
// action's author, and it's revision.
type Description struct {
	Author   string  `json:"author,omitempty"`
	Email    string  `json:"email,omitempty"`
	URL      string  `json:"url,omitempty"`
	Revision float64 `json:"revision,omitempty"`
}

// a threat provides the investigator with an idea of how dangerous
// a the compromission might be, if the indicators return positive
type Threat struct {
	Ref    string `json:"ref,omitempty"`
	Level  string `json:"level,omitempty"`
	Family string `json:"family,omitempty"`
	Type   string `json:"type,omitempty"`
}

// an operation is an object that maps to an agent module.
// the parameters of the operation are passed to the module as an argument,
// and thus their format depends on the module itself.
type Operation struct {
	Module     string      `json:"module"`
	Parameters interface{} `json:"parameters"`

	// If WantCompressed is set in the operation, the parameters
	// will be compressed in PostAction() when the client sends the
	// action to the API. This will also result in IsCompressed being
	// marked as true, so the receiving agent knows it must decompress
	// the parameter data.
	IsCompressed   bool `json:"is_compressed,omitempty"`
	WantCompressed bool `json:"want_compressed,omitempty"`
}

// Compress the parameters stored within an operation
func (op *Operation) CompressOperationParam() (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("CompressOperationParam() -> %v", e)
		}
	}()
	jb, err := json.Marshal(op.Parameters)
	if err != nil {
		panic(err)
	}
	var b bytes.Buffer
	wb64 := base64.NewEncoder(base64.StdEncoding, &b)
	w := gzip.NewWriter(wb64)
	_, err = w.Write(jb)
	if err != nil {
		panic(err)
	}
	w.Close()
	wb64.Close()
	op.Parameters = string(b.Bytes())
	op.IsCompressed = true
	return
}

// Decompress the parameters stored within an operation
func (op *Operation) DecompressOperationParam() (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("DecompressOperationParam() -> %v", e)
		}
	}()
	if !op.IsCompressed {
		return nil
	}
	pstr, ok := op.Parameters.(string)
	if !ok {
		panic("Compressed parameter was not a string")
	}
	b := bytes.NewBuffer([]byte(pstr))
	rb64 := base64.NewDecoder(base64.StdEncoding, b)
	r, err := gzip.NewReader(rb64)
	if err != nil {
		panic(err)
	}
	rb, err := ioutil.ReadAll(r)
	if err != nil {
		panic(err)
	}
	err = json.Unmarshal(rb, &op.Parameters)
	if err != nil {
		panic(err)
	}
	op.IsCompressed = false
	return
}

// ActionFromFile() reads an action from a local file on the file system
// and returns a mig.Action structure
func ActionFromFile(path string) (Action, error) {
	var err error
	var a Action
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("mig.ActionFromFile(): %v", e)
		}
	}()
	// parse the json of the action into a mig.Action
	fd, err := ioutil.ReadFile(path)
	if err != nil {
		return a, err
	}
	err = json.Unmarshal(fd, &a)
	if err != nil {
		return a, err
	}

	return a, err
}

// ToTempFile writes an action into a generated temporary file and returns its filename
func (a Action) ToTempFile() (filename string, err error) {
	var (
		data []byte
		fd   *os.File
		fi   os.FileInfo
	)
	data, err = json.Marshal(a)
	if err != nil {
		return
	}
	fd, err = ioutil.TempFile("", "migaction_")
	defer fd.Close()
	if err != nil {
		return
	}
	_, err = fd.Write(data)
	if err != nil {
		return
	}
	fi, err = fd.Stat()
	if err != nil {
		return
	}
	filename = fmt.Sprintf("%s/%s", os.TempDir(), fi.Name())
	return
}

type id struct {
	value float64
	sync.Mutex
}

var globalID id

// GenID() returns a float64 ID number that is unique to this process. The ID is initialized
// at the number of seconds since MIG's creation date, shifted 16 bits to the right and incremented
// by one every time a new ID is requested. The resulting value must fit in 53 bits of precision
// provided by the float64 type.
func GenID() float64 {
	globalID.Lock()
	defer globalID.Unlock()
	if globalID.value < 1 {
		// if id hasn't been initialized yet, set it to number of seconds since
		// MIG's inception, plus one
		tmpid := int64(time.Since(time.Unix(1367258400, 0)).Seconds() + 1)
		tmpid = tmpid << 16
		globalID.value = float64(tmpid)
		return globalID.value
	} else {
		globalID.value++
		return globalID.value
	}
}

// GenHexID returns a string with an hexadecimal encoded ID
func GenB32ID() string {
	id := GenID()
	return strconv.FormatUint(uint64(id), 36)
}

// Validate verifies that the Action received contained all the
// necessary fields, and returns an error when it doesn't.
func (a Action) Validate() (err error) {
	if a.Name == "" {
		return errors.New("Action.Name is empty. Expecting string.")
	}
	if a.Target == "" {
		return errors.New("Action.Target is empty. Expecting string.")
	}
	if a.SyntaxVersion != ActionVersion {
		return fmt.Errorf("Wrong Syntax Version integer. Expection version %d", ActionVersion)
	}
	if a.ValidFrom.String() == "" {
		return errors.New("Action.ValidFrom is empty. Expecting string.")
	}
	if a.ExpireAfter.String() == "" {
		return errors.New("Action.ExpireAfter is empty. Expecting string.")
	}
	if a.ValidFrom.After(a.ExpireAfter) {
		return errors.New("Action.ExpireAfter is set before Action.ValidFrom.")
	}
	if time.Now().After(a.ExpireAfter) {
		return errors.New("Action.ExpireAfter is passed. Action has expired.")
	}
	if a.Operations == nil {
		return errors.New("Action.Operations is nil. Expecting string.")
	}
	if len(a.PGPSignatures) < 1 {
		return errors.New("Action.PGPSignatures is empty. Expecting array of strings.")
	}
	return
}

// Sign computes and returns the GPG signature of a MIG action in its stringified form
func (a Action) Sign(keyid string, secring io.Reader) (sig string, err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("Sign() -> %v", e)
		}
	}()
	filename, err := a.ToTempFile()
	if err != nil {
		panic(err)
	}
	a2, err := ActionFromFile(filename)
	if err != nil {
		panic(err)
	}
	err = os.Remove(filename)
	if err != nil {
		panic(err)
	}
	str, err := a2.String()
	if err != nil {
		panic(err)
	}
	sig, err = pgp.Sign(str, keyid, secring)
	if err != nil {
		panic(err)
	}
	return
}

// VerifySignatures verifies that the Action contains valid signatures from
// known investigators. It does not verify permissions.
func (a Action) VerifySignatures(keyring io.Reader) (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("VerifySignatures() -> %v", e)
		}
	}()
	astr, err := a.String()
	if err != nil {
		return errors.New("Failed to stringify action")
	}
	// Create a copy of the keyring we can use during validation of each
	// signature. We don't want to use the keyring reader directly as it is
	// backed by a buffer and will be drained after verification of the first
	// signature.
	keycopy, err := ioutil.ReadAll(keyring)
	if err != nil {
		panic(err)
	}
	for _, sig := range a.PGPSignatures {
		keyrdr := bytes.NewBuffer(keycopy)
		valid, _, err := pgp.Verify(astr, sig, keyrdr)
		if err != nil {
			return errors.New("Failed to verify PGP Signature")
		}
		if !valid {
			return errors.New("Invalid PGP Signature")
		}
	}
	return
}

//  concatenates Action components into a string
func (a Action) String() (str string, err error) {
	args, err := json.Marshal(a.Operations)
	if err != nil {
		return
	}
	str += fmt.Sprintf("name=%s;target=%s;validfrom=%d;expireafter=%s;operations=%s;",
		a.Name, a.Target, a.ValidFrom.UTC().Unix(), a.ExpireAfter.UTC().Unix(), args)
	return
}

// VerifyACL controls that an action has been issued by investigators
// that have the right permissions. This function looks at each operation
// listed in the action, and find the corresponding permission. If no
// permission is found, the default one `default` is used.
// The first permission that is found to apply to an operation, but
// doesn't allow the operation to run, will fail the verification globally
func (a Action) VerifyACL(acl ACL, keyring io.Reader, onlyVerifyPubKey bool) (err error) {
	// first, verify all signatures and get a list of PGP
	// fingerprints of the signers
	var fingerprints []string
	astr, err := a.String()
	if err != nil {
		return errors.New("Failed to stringify action")
	}
	// Create a copy of the keyring we can use during validation of each
	// signature. We don't want to use the keyring reader directly as it is
	// backed by a buffer and will be drained after verification of the first
	// signature.
	keycopy, err := ioutil.ReadAll(keyring)
	if err != nil {
		panic(err)
	}
	for _, sig := range a.PGPSignatures {
		keyrdr := bytes.NewBuffer(keycopy)
		fp, err := pgp.GetFingerprintFromSignature(astr, sig, keyrdr)
		if err != nil {
			return fmt.Errorf("Failed to retrieve fingerprint from signatures: %v", err)
		}
		fingerprints = append(fingerprints, fp)
	}

	if len(fingerprints) == 0 {
		return errors.New("No valid fingerprints found.")
	}

	if onlyVerifyPubKey {
		return
	}

	// Authorize access to the operation by verifying the fingerprints present against our
	// ACLs
	for _, operation := range a.Operations {
		err = verifyPermission(operation.Module, acl, fingerprints)
		if err != nil {
			return err
		}
	}
	return
}

// PrintCounters prints the counters of an action to stderr
func (a Action) PrintCounters() {
	out := fmt.Sprintf("%d sent, %d done", a.Counters.Sent, a.Counters.Done)
	if a.Counters.InFlight > 0 {
		out += fmt.Sprintf(", %d inflight", a.Counters.InFlight)
	}
	if a.Counters.Success > 0 {
		out += fmt.Sprintf(", %d succeeded", a.Counters.Success)
	}
	if a.Counters.Cancelled > 0 {
		out += fmt.Sprintf(", %d cancelled", a.Counters.Cancelled)
	}
	if a.Counters.Expired > 0 {
		out += fmt.Sprintf(", %d expired", a.Counters.Expired)
	}
	if a.Counters.Failed > 0 {
		out += fmt.Sprintf(", %d failed", a.Counters.Failed)
	}
	if a.Counters.TimeOut > 0 {
		out += fmt.Sprintf(", %d timed out", a.Counters.TimeOut)
	}
	fmt.Fprintf(os.Stderr, "%s\n", out)
}

// Return the an indented JSON string representing the action suitable for
// display
func (a Action) IndentedString() (string, error) {
	buf, err := json.MarshalIndent(a, "", "    ")
	if err != nil {
		return "", err
	}
	return string(buf), nil
}
<commit_msg>return error in Action.VerifySignatures if action contains no signatures<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor: Julien Vehent jvehent@mozilla.com [:ulfr]

package mig /* import "mig.ninja/mig" */

import (
	"bytes"
	"compress/gzip"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"strconv"
	"sync"
	"time"

	"mig.ninja/mig/pgp"
)

// ActionVersion is the version of the syntax that is expected
const ActionVersion uint16 = 2

// an Action is the json object that is created by an investigator
// and provided to the MIG platform. It must be PGP signed.
type Action struct {
	ID             float64        `json:"id"`
	Name           string         `json:"name"`
	Target         string         `json:"target"`
	Description    Description    `json:"description,omitempty"`
	Threat         Threat         `json:"threat,omitempty"`
	ValidFrom      time.Time      `json:"validfrom"`
	ExpireAfter    time.Time      `json:"expireafter"`
	Operations     []Operation    `json:"operations"`
	PGPSignatures  []string       `json:"pgpsignatures"`
	Investigators  []Investigator `json:"investigators,omitempty"`
	Status         string         `json:"status,omitempty"`
	StartTime      time.Time      `json:"starttime,omitempty"`
	FinishTime     time.Time      `json:"finishtime,omitempty"`
	LastUpdateTime time.Time      `json:"lastupdatetime,omitempty"`
	Counters       ActionCounters `json:"counters,omitempty"`
	SyntaxVersion  uint16         `json:"syntaxversion,omitempty"`
}

// Some counters used to track the completion of an action
type ActionCounters struct {
	Sent      int `json:"sent,omitempty"`
	Done      int `json:"done,omitempty"`
	InFlight  int `json:"inflight,omitempty"`
	Success   int `json:"success,omitempty"`
	Cancelled int `json:"cancelled,omitempty"`
	Expired   int `json:"expired,omitempty"`
	Failed    int `json:"failed,omitempty"`
	TimeOut   int `json:"timeout,omitempty"`
}

// a description is a simple object that contains detail about the
// action's author, and it's revision.
type Description struct {
	Author   string  `json:"author,omitempty"`
	Email    string  `json:"email,omitempty"`
	URL      string  `json:"url,omitempty"`
	Revision float64 `json:"revision,omitempty"`
}

// a threat provides the investigator with an idea of how dangerous
// a the compromission might be, if the indicators return positive
type Threat struct {
	Ref    string `json:"ref,omitempty"`
	Level  string `json:"level,omitempty"`
	Family string `json:"family,omitempty"`
	Type   string `json:"type,omitempty"`
}

// an operation is an object that maps to an agent module.
// the parameters of the operation are passed to the module as an argument,
// and thus their format depends on the module itself.
type Operation struct {
	Module     string      `json:"module"`
	Parameters interface{} `json:"parameters"`

	// If WantCompressed is set in the operation, the parameters
	// will be compressed in PostAction() when the client sends the
	// action to the API. This will also result in IsCompressed being
	// marked as true, so the receiving agent knows it must decompress
	// the parameter data.
	IsCompressed   bool `json:"is_compressed,omitempty"`
	WantCompressed bool `json:"want_compressed,omitempty"`
}

// Compress the parameters stored within an operation
func (op *Operation) CompressOperationParam() (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("CompressOperationParam() -> %v", e)
		}
	}()
	jb, err := json.Marshal(op.Parameters)
	if err != nil {
		panic(err)
	}
	var b bytes.Buffer
	wb64 := base64.NewEncoder(base64.StdEncoding, &b)
	w := gzip.NewWriter(wb64)
	_, err = w.Write(jb)
	if err != nil {
		panic(err)
	}
	w.Close()
	wb64.Close()
	op.Parameters = string(b.Bytes())
	op.IsCompressed = true
	return
}

// Decompress the parameters stored within an operation
func (op *Operation) DecompressOperationParam() (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("DecompressOperationParam() -> %v", e)
		}
	}()
	if !op.IsCompressed {
		return nil
	}
	pstr, ok := op.Parameters.(string)
	if !ok {
		panic("Compressed parameter was not a string")
	}
	b := bytes.NewBuffer([]byte(pstr))
	rb64 := base64.NewDecoder(base64.StdEncoding, b)
	r, err := gzip.NewReader(rb64)
	if err != nil {
		panic(err)
	}
	rb, err := ioutil.ReadAll(r)
	if err != nil {
		panic(err)
	}
	err = json.Unmarshal(rb, &op.Parameters)
	if err != nil {
		panic(err)
	}
	op.IsCompressed = false
	return
}

// ActionFromFile() reads an action from a local file on the file system
// and returns a mig.Action structure
func ActionFromFile(path string) (Action, error) {
	var err error
	var a Action
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("mig.ActionFromFile(): %v", e)
		}
	}()
	// parse the json of the action into a mig.Action
	fd, err := ioutil.ReadFile(path)
	if err != nil {
		return a, err
	}
	err = json.Unmarshal(fd, &a)
	if err != nil {
		return a, err
	}

	return a, err
}

// ToTempFile writes an action into a generated temporary file and returns its filename
func (a Action) ToTempFile() (filename string, err error) {
	var (
		data []byte
		fd   *os.File
		fi   os.FileInfo
	)
	data, err = json.Marshal(a)
	if err != nil {
		return
	}
	fd, err = ioutil.TempFile("", "migaction_")
	defer fd.Close()
	if err != nil {
		return
	}
	_, err = fd.Write(data)
	if err != nil {
		return
	}
	fi, err = fd.Stat()
	if err != nil {
		return
	}
	filename = fmt.Sprintf("%s/%s", os.TempDir(), fi.Name())
	return
}

type id struct {
	value float64
	sync.Mutex
}

var globalID id

// GenID() returns a float64 ID number that is unique to this process. The ID is initialized
// at the number of seconds since MIG's creation date, shifted 16 bits to the right and incremented
// by one every time a new ID is requested. The resulting value must fit in 53 bits of precision
// provided by the float64 type.
func GenID() float64 {
	globalID.Lock()
	defer globalID.Unlock()
	if globalID.value < 1 {
		// if id hasn't been initialized yet, set it to number of seconds since
		// MIG's inception, plus one
		tmpid := int64(time.Since(time.Unix(1367258400, 0)).Seconds() + 1)
		tmpid = tmpid << 16
		globalID.value = float64(tmpid)
		return globalID.value
	} else {
		globalID.value++
		return globalID.value
	}
}

// GenHexID returns a string with an hexadecimal encoded ID
func GenB32ID() string {
	id := GenID()
	return strconv.FormatUint(uint64(id), 36)
}

// Validate verifies that the Action received contained all the
// necessary fields, and returns an error when it doesn't.
func (a Action) Validate() (err error) {
	if a.Name == "" {
		return errors.New("Action.Name is empty. Expecting string.")
	}
	if a.Target == "" {
		return errors.New("Action.Target is empty. Expecting string.")
	}
	if a.SyntaxVersion != ActionVersion {
		return fmt.Errorf("Wrong Syntax Version integer. Expection version %d", ActionVersion)
	}
	if a.ValidFrom.String() == "" {
		return errors.New("Action.ValidFrom is empty. Expecting string.")
	}
	if a.ExpireAfter.String() == "" {
		return errors.New("Action.ExpireAfter is empty. Expecting string.")
	}
	if a.ValidFrom.After(a.ExpireAfter) {
		return errors.New("Action.ExpireAfter is set before Action.ValidFrom.")
	}
	if time.Now().After(a.ExpireAfter) {
		return errors.New("Action.ExpireAfter is passed. Action has expired.")
	}
	if a.Operations == nil {
		return errors.New("Action.Operations is nil. Expecting string.")
	}
	if len(a.PGPSignatures) < 1 {
		return errors.New("Action.PGPSignatures is empty. Expecting array of strings.")
	}
	return
}

// Sign computes and returns the GPG signature of a MIG action in its stringified form
func (a Action) Sign(keyid string, secring io.Reader) (sig string, err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("Sign() -> %v", e)
		}
	}()
	filename, err := a.ToTempFile()
	if err != nil {
		panic(err)
	}
	a2, err := ActionFromFile(filename)
	if err != nil {
		panic(err)
	}
	err = os.Remove(filename)
	if err != nil {
		panic(err)
	}
	str, err := a2.String()
	if err != nil {
		panic(err)
	}
	sig, err = pgp.Sign(str, keyid, secring)
	if err != nil {
		panic(err)
	}
	return
}

// VerifySignatures verifies that the Action contains valid signatures from
// known investigators. It does not verify permissions.
func (a Action) VerifySignatures(keyring io.Reader) (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("VerifySignatures() -> %v", e)
		}
	}()
	astr, err := a.String()
	if err != nil {
		return errors.New("Failed to stringify action")
	}
	// If the action does not contain any signatures, treat this as an error condition
	if a.PGPSignatures == nil || len(a.PGPSignatures) == 0 {
		return errors.New("Action contained no valid signatures")
	}
	// Create a copy of the keyring we can use during validation of each
	// signature. We don't want to use the keyring reader directly as it is
	// backed by a buffer and will be drained after verification of the first
	// signature.
	keycopy, err := ioutil.ReadAll(keyring)
	if err != nil {
		panic(err)
	}
	for _, sig := range a.PGPSignatures {
		keyrdr := bytes.NewBuffer(keycopy)
		valid, _, err := pgp.Verify(astr, sig, keyrdr)
		if err != nil {
			return errors.New("Failed to verify PGP Signature")
		}
		if !valid {
			return errors.New("Invalid PGP Signature")
		}
	}
	return
}

//  concatenates Action components into a string
func (a Action) String() (str string, err error) {
	args, err := json.Marshal(a.Operations)
	if err != nil {
		return
	}
	str += fmt.Sprintf("name=%s;target=%s;validfrom=%d;expireafter=%s;operations=%s;",
		a.Name, a.Target, a.ValidFrom.UTC().Unix(), a.ExpireAfter.UTC().Unix(), args)
	return
}

// VerifyACL controls that an action has been issued by investigators
// that have the right permissions. This function looks at each operation
// listed in the action, and find the corresponding permission. If no
// permission is found, the default one `default` is used.
// The first permission that is found to apply to an operation, but
// doesn't allow the operation to run, will fail the verification globally
func (a Action) VerifyACL(acl ACL, keyring io.Reader, onlyVerifyPubKey bool) (err error) {
	// first, verify all signatures and get a list of PGP
	// fingerprints of the signers
	var fingerprints []string
	astr, err := a.String()
	if err != nil {
		return errors.New("Failed to stringify action")
	}
	// Create a copy of the keyring we can use during validation of each
	// signature. We don't want to use the keyring reader directly as it is
	// backed by a buffer and will be drained after verification of the first
	// signature.
	keycopy, err := ioutil.ReadAll(keyring)
	if err != nil {
		panic(err)
	}
	for _, sig := range a.PGPSignatures {
		keyrdr := bytes.NewBuffer(keycopy)
		fp, err := pgp.GetFingerprintFromSignature(astr, sig, keyrdr)
		if err != nil {
			return fmt.Errorf("Failed to retrieve fingerprint from signatures: %v", err)
		}
		fingerprints = append(fingerprints, fp)
	}

	if len(fingerprints) == 0 {
		return errors.New("No valid fingerprints found.")
	}

	if onlyVerifyPubKey {
		return
	}

	// Authorize access to the operation by verifying the fingerprints present against our
	// ACLs
	for _, operation := range a.Operations {
		err = verifyPermission(operation.Module, acl, fingerprints)
		if err != nil {
			return err
		}
	}
	return
}

// PrintCounters prints the counters of an action to stderr
func (a Action) PrintCounters() {
	out := fmt.Sprintf("%d sent, %d done", a.Counters.Sent, a.Counters.Done)
	if a.Counters.InFlight > 0 {
		out += fmt.Sprintf(", %d inflight", a.Counters.InFlight)
	}
	if a.Counters.Success > 0 {
		out += fmt.Sprintf(", %d succeeded", a.Counters.Success)
	}
	if a.Counters.Cancelled > 0 {
		out += fmt.Sprintf(", %d cancelled", a.Counters.Cancelled)
	}
	if a.Counters.Expired > 0 {
		out += fmt.Sprintf(", %d expired", a.Counters.Expired)
	}
	if a.Counters.Failed > 0 {
		out += fmt.Sprintf(", %d failed", a.Counters.Failed)
	}
	if a.Counters.TimeOut > 0 {
		out += fmt.Sprintf(", %d timed out", a.Counters.TimeOut)
	}
	fmt.Fprintf(os.Stderr, "%s\n", out)
}

// Return the an indented JSON string representing the action suitable for
// display
func (a Action) IndentedString() (string, error) {
	buf, err := json.MarshalIndent(a, "", "    ")
	if err != nil {
		return "", err
	}
	return string(buf), nil
}
<|endoftext|>


<commit_before>/*
 * The MIT License (MIT)
 *
 *  Copyright (c) 2014 Stephen Parker (withaspark.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package main

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
)

// An action structure for pushing to
type action struct {
	url  string
	body []byte
}

// Instantiates a new action object.
// Message used because we push all actions to the same domain as the to
// address in email. E.g., email to foo+add@example.com will push to
// http://example.com/foo/add.
func NewAction(m Message) (*action, error) {
	// Build an action object
	a := new(action)

	// Build url to push to
	a.url = "http"
	if OptToHTTPS {
		a.url += "s"
	}
	a.url += "://" + m.GetDomain() + m.GetPath()

	// Convert message object to JSON
	tempBody, err := json.Marshal(m)
	if err != nil {
		return nil, fmt.Errorf("Failed to encode message for send. %v", err)
	}
	a.body = tempBody

	return a, nil
}

// Initiates the HTTP request
func (a *action) Do() error {
	// Make call to API
	BuildJSONPost(a.url, a.body)

	// Push record to global datastore
	BuildJSONPost(OptDataStoreUrl, a.body)

	// Get mailsSent counter from global datastore
	sMailsSent, getCountErr := BuildJSONGet(OptDataStoreCountUrl)
	if getCountErr != nil {
		return fmt.Errorf("Error: Failed to get count of mails sent. %v", getCountErr)
	}

	// Parse count and increment
	var MailsSent map[string]int
	unmarshErr := json.Unmarshal([]byte(sMailsSent), &MailsSent)
	if unmarshErr != nil {
		return fmt.Errorf("Error: Failed to unmarshal mailsSent. %v", unmarshErr)
	}
	sMailsSent = fmt.Sprintf("{\"count\": %d}", MailsSent["count"]+1)

	// Update mailsSent count in global datastore
	BuildJSONPatch(OptDataStoreCountUrl, []byte(sMailsSent))

	return nil
}

// Builds a json encoded HTTP PATCH request
func BuildJSONPatch(url string, content []byte) (string, error) {
	return BuildJSONRequest("PATCH", url, content)
}

// Builds a json encoded HTTP POST request
func BuildJSONPost(url string, content []byte) (string, error) {
	return BuildJSONRequest("POST", url, content)
}

// Builds an HTTP GET request
func BuildJSONGet(url string) (string, error) {
	return BuildJSONRequest("GET", url, nil)
}

// Builds a generic HTTP request, handles errors and logging
func BuildJSONRequest(reqType string, url string, content []byte) (string, error) {
	// Check url valid
	//TODO: Add better url test
	if len(url) < 1 {
		return "", fmt.Errorf("Invalid URL %s", url)
	}

	// Make request to url containing JSON content
	request, err := http.NewRequest(reqType, url, bytes.NewBuffer(content))
	if err != nil {
		return "", fmt.Errorf("Failed to build request. %v", err)
	}

	// Display/log request for debugging
	log.Printf("Request: [%s] %s", url, content)

	// Set request headers
	request.Header.Set("Content-Type", "application/json")

	// Initiate HTTP client
	clientHandler := &http.Client{
		Transport: &http.Transport{
			// Allow certs with name mismatch
			//FIXME: This needs to be addressed, susceptible to
			//       MITM attacks. Bug 154.
			TLSClientConfig: &tls.Config{
				InsecureSkipVerify: true,
			},
		},
	}
	response, err := clientHandler.Do(request)

	// Check for errors on response
	if err != nil {
		return "", fmt.Errorf("Failed to get response from %s. %v", url, err)
	}
	defer response.Body.Close()

	// Display/log response
	body, _ := ioutil.ReadAll(response.Body)
	log.Printf("Response: [%s] %s %s", response.Status, response.Header, string(body))

	return string(body), nil
}

<commit_msg>Bug 153: Fix handling of count to avoid race conditions between multiple servers talking to same backend.<commit_after>/*
 * The MIT License (MIT)
 *
 *  Copyright (c) 2014 Stephen Parker (withaspark.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package main

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
)

// An action structure for pushing to
type action struct {
	url  string
	body []byte
}

// Instantiates a new action object.
// Message used because we push all actions to the same domain as the to
// address in email. E.g., email to foo+add@example.com will push to
// http://example.com/foo/add.
func NewAction(m Message) (*action, error) {
	// Build an action object
	a := new(action)

	// Build url to push to
	a.url = "http"
	if OptToHTTPS {
		a.url += "s"
	}
	a.url += "://" + m.GetDomain() + m.GetPath()

	// Convert message object to JSON
	tempBody, err := json.Marshal(m)
	if err != nil {
		return nil, fmt.Errorf("Failed to encode message for send. %v", err)
	}
	a.body = tempBody

	return a, nil
}

// Initiates the HTTP request
func (a *action) Do() error {
	// Make call to API
	BuildJSONPost(a.url, a.body)

	// Push record to global datastore
	BuildJSONPost(OptDataStoreUrl, a.body)

	// Get mailsSent counter from global datastore
	sMailsSent, getCountErr := BuildJSONGet(OptDataStoreCountUrl)
	if getCountErr != nil {
		return fmt.Errorf("Error: Failed to get count of mails sent. %v", getCountErr)
	}

	// Get current count of mails sent
	var MailsSent map[string]int
	unmarshErr := json.Unmarshal([]byte(sMailsSent), &MailsSent)
	if unmarshErr != nil {
		return fmt.Errorf("Error: Failed to unmarshal mailsSent. %v", unmarshErr)
	}

	// Update mailsSent count in global datastore, try iMaxRetries times
	var updateCountErr error
	var sUpdateResp string
	var UpdateResp map[string]string
	const iMaxRetries int = 100
	for iRetries := 1; iRetries <= iMaxRetries; iRetries++ {
		// Send patch queries until we have incremented the count
		// The backend datastore will return an error json response if new value isn't greater than old
		sUpdateResp, updateCountErr = BuildJSONPatch(OptDataStoreCountUrl, []byte(fmt.Sprintf("{\"count\": %d}", MailsSent["count"]+iRetries)))
		unmarshErr := json.Unmarshal([]byte(sUpdateResp), &UpdateResp)
		if unmarshErr != nil {
			return fmt.Errorf("Error: Failed to unmarshal mails sent count update. %v", unmarshErr)
		}

		// Was a count response returned
		if _, success := UpdateResp["count"]; success {
			updateCountErr = nil
			break
		} else {
			updateCountErr = fmt.Errorf("Error: Failed to update sent mail count")
		}
	}

	return updateCountErr
}

// Builds a json encoded HTTP PATCH request
func BuildJSONPatch(url string, content []byte) (string, error) {
	return BuildJSONRequest("PATCH", url, content)
}

// Builds a json encoded HTTP POST request
func BuildJSONPost(url string, content []byte) (string, error) {
	return BuildJSONRequest("POST", url, content)
}

// Builds an HTTP GET request
func BuildJSONGet(url string) (string, error) {
	return BuildJSONRequest("GET", url, nil)
}

// Builds a generic HTTP request, handles errors and logging
func BuildJSONRequest(reqType string, url string, content []byte) (string, error) {
	// Check url valid
	//TODO: Add better url test
	if len(url) < 1 {
		return "", fmt.Errorf("Invalid URL %s", url)
	}

	// Make request to url containing JSON content
	request, err := http.NewRequest(reqType, url, bytes.NewBuffer(content))
	if err != nil {
		return "", fmt.Errorf("Failed to build request. %v", err)
	}

	// Display/log request for debugging
	log.Printf("Request: [%s] %s", url, content)

	// Set request headers
	request.Header.Set("Content-Type", "application/json")

	// Initiate HTTP client
	clientHandler := &http.Client{
		Transport: &http.Transport{
			// Allow certs with name mismatch
			//FIXME: This needs to be addressed, susceptible to
			//       MITM attacks. Bug 154.
			TLSClientConfig: &tls.Config{
				InsecureSkipVerify: true,
			},
		},
	}
	response, err := clientHandler.Do(request)

	// Check for errors on response
	if err != nil {
		return "", fmt.Errorf("Failed to get response from %s. %v", url, err)
	}
	defer response.Body.Close()

	// Display/log response
	body, _ := ioutil.ReadAll(response.Body)
	log.Printf("Response: [%s] %s %s", response.Status, response.Header, string(body))

	return string(body), nil
}

<|endoftext|>


<commit_before>package commander

// The following are ACTION functions, chose one if you like it.
type (
	Action             func(c Context) Result // default internal function
	ActionNormal       func(c Context) error
	ActionSimple       func(c Context)
	ActionNative       func()
	ActionNativeSimple func() error
	ActionNativeDocopt func(m map[string]interface{}) error
)

func parseAction(arg interface{}) (a Action) {
	switch action := arg.(type) {
	case Action:
		a = action
	case ActionNormal:
		a = func(c Context) Result {
			if err := action(c); err != nil {
				return NewResultError(err)
			}
			return ResultPass
		}
	case ActionSimple:
		a = func(c Context) Result {
			action(c)
			return ResultPass
		}
	case ActionNative:
		a = func(c Context) Result {
			action()
			return ResultPass
		}
	case ActionNativeSimple:
		a = func(c Context) Result {
			action()
			return ResultPass
		}
	case ActionNativeDocopt:
		a = func(c Context) Result {
			if err := action(c.Map()); err != nil {
				return NewResultError(err)
			}
			return ResultPass
		}
	default:
		a = nil
	}
	return
}
<commit_msg>fix: action parse function<commit_after>package commander

// The following are ACTION functions, chose one if you like it.
type (
	Action             func(c Context) Result // default internal function
	ActionNormal       func(c Context) error
	ActionSimple       func(c Context)
	ActionNative       func()
	ActionNativeSimple func() error
	ActionNativeDocopt func(m map[string]interface{}) error
)

// parseAction handle function to Action type
func parseAction(arg interface{}) (a Action) {
	switch action := arg.(type) {
	case func(c Context) Result: // Action
		a = action
	case func(c Context) error: // ActionNormal
		a = func(c Context) Result {
			if err := action(c); err != nil {
				return NewResultError(err)
			}
			return ResultPass
		}
	case func(c Context): // ActionSimple
		a = func(c Context) Result {
			action(c)
			return ResultPass
		}
	case func(): // ActionNative
		a = func(c Context) Result {
			action()
			return ResultPass
		}
	case func() error: // ActionNativeSimple
		a = func(c Context) Result {
			action()
			return ResultPass
		}
	case func(m map[string]interface{}) error: // ActionNativeDocopt
		a = func(c Context) Result {
			if err := action(c.Map()); err != nil {
				return NewResultError(err)
			}
			return ResultPass
		}
	default:
		a = nil
	}
	return
}
<|endoftext|>


<commit_before>package lmdb

/*
#include <stdlib.h>
#include <stdio.h>
#include "lmdb.h"
*/
import "C"

import (
	"errors"
	"fmt"
	"os"
	"runtime"
	"unsafe"
)

// success is a value returned from the LMDB API to indicate a successful call.
// The functions in this API this behavior and its use is not required.
const success = C.MDB_SUCCESS

const (
	// Flags for Env.Open.
	//
	// See mdb_env_open
	FixedMap    = C.MDB_FIXEDMAP   // Danger zone. Map memory at a fixed address.
	NoSubdir    = C.MDB_NOSUBDIR   // Argument to Open is a file, not a directory.
	Readonly    = C.MDB_RDONLY     // Used in several functions to denote an object as readonly.
	WriteMap    = C.MDB_WRITEMAP   // Use a writable memory map.
	NoMetaSync  = C.MDB_NOMETASYNC // Don't fsync metapage after commit.
	NoSync      = C.MDB_NOSYNC     // Don't fsync after commit.
	MapAsync    = C.MDB_MAPASYNC   // Flush asynchronously when using the WriteMap flag.
	NoTLS       = C.MDB_NOTLS      // Danger zone. When unset reader locktable slots are tied to their thread.
	NoLock      = C.MDB_NOLOCK     // Danger zone. LMDB does not use any locks.
	NoReadahead = C.MDB_NORDAHEAD  // Disable readahead. Requires OS support.
	NoMemInit   = C.MDB_NOMEMINIT  // Disable LMDB memory initialization.
)

// DBI is a handle for a database in an Env.
//
// See MDB_dbi
type DBI C.MDB_dbi

// Env is opaque structure for a database environment.  A DB environment
// supports multiple databases, all residing in the same shared-memory map.
//
// See MDB_env.
type Env struct {
	_env *C.MDB_env
}

// NewEnv allocates and initializes a new Env.
//
// See mdb_env_create.
func NewEnv() (*Env, error) {
	var _env *C.MDB_env
	ret := C.mdb_env_create(&_env)
	if ret != success {
		return nil, operrno("mdb_env_create", ret)
	}
	return &Env{_env}, nil
}

// Open an environment handle. If this function fails Close() must be called to
// discard the Env handle.  Open passes flags|NoTLS to mdb_env_open.
//
// See mdb_env_open.
func (env *Env) Open(path string, flags uint, mode os.FileMode) error {
	cpath := C.CString(path)
	defer C.free(unsafe.Pointer(cpath))
	ret := C.mdb_env_open(env._env, cpath, C.uint(NoTLS|flags), C.mdb_mode_t(mode))
	return operrno("mdb_env_open", ret)
}

// Close shuts down the environment and releases the memory map.
//
// See mdb_env_close.
func (env *Env) Close() error {
	if env._env == nil {
		return errors.New("Environment already closed")
	}
	C.mdb_env_close(env._env)
	env._env = nil
	return nil
}

// Copy copies the data in env to an environment at path.
//
// See mdb_env_copy.
func (env *Env) Copy(path string) error {
	cpath := C.CString(path)
	defer C.free(unsafe.Pointer(cpath))
	ret := C.mdb_env_copy(env._env, cpath)
	return operrno("mdb_env_copy", ret)
}

// CopyFlag copies the data in env to an environment at path created with flags.
//
// See mdb_env_copy2.
func (env *Env) CopyFlag(path string, flags uint) error {
	cpath := C.CString(path)
	defer C.free(unsafe.Pointer(cpath))
	ret := C.mdb_env_copy2(env._env, cpath, C.uint(flags))
	return operrno("mdb_env_copy2", ret)
}

// Statistics for a database in the environment
//
// See MDB_stat.
type Stat struct {
	PSize         uint   // Size of a database page. This is currently the same for all databases.
	Depth         uint   // Depth (height) of the B-tree
	BranchPages   uint64 // Number of internal (non-leaf) pages
	LeafPages     uint64 // Number of leaf pages
	OverflowPages uint64 // Number of overflow pages
	Entries       uint64 // Number of data items
}

// Stat returns statistics about the environment.
//
// See mdb_env_stat.
func (env *Env) Stat() (*Stat, error) {
	var _stat C.MDB_stat
	ret := C.mdb_env_stat(env._env, &_stat)
	if ret != success {
		return nil, operrno("mdb_env_stat", ret)
	}
	stat := Stat{PSize: uint(_stat.ms_psize),
		Depth:         uint(_stat.ms_depth),
		BranchPages:   uint64(_stat.ms_branch_pages),
		LeafPages:     uint64(_stat.ms_leaf_pages),
		OverflowPages: uint64(_stat.ms_overflow_pages),
		Entries:       uint64(_stat.ms_entries)}
	return &stat, nil
}

// Information about the environment.
//
// See MDB_envinfo.
type EnvInfo struct {
	MapSize    int64 // Size of the data memory map
	LastPNO    int64 // ID of the last used page
	LastTxnID  int64 // ID of the last committed transaction
	MaxReaders uint  // maximum number of threads for the environment
	NumReaders uint  // maximum number of threads used in the environment
}

// Info returns information about the environment.
//
// See mdb_env_info.
func (env *Env) Info() (*EnvInfo, error) {
	var _info C.MDB_envinfo
	ret := C.mdb_env_info(env._env, &_info)
	if ret != success {
		return nil, operrno("mdb_env_info", ret)
	}
	info := EnvInfo{
		MapSize:    int64(_info.me_mapsize),
		LastPNO:    int64(_info.me_last_pgno),
		LastTxnID:  int64(_info.me_last_txnid),
		MaxReaders: uint(_info.me_maxreaders),
		NumReaders: uint(_info.me_numreaders),
	}
	return &info, nil
}

// Sync flushes buffers to disk.  If force is true a synchronous flush occurs
// and ignores any NoSync or MapAsync flag on the environment.
//
// See mdb_env_sync.
func (env *Env) Sync(force bool) error {
	ret := C.mdb_env_sync(env._env, cbool(force))
	return operrno("mdb_env_sync", ret)
}

// SetFlags sets flags in the environment.
//
// See mdb_env_set_flags.
func (env *Env) SetFlags(flags uint) error {
	ret := C.mdb_env_set_flags(env._env, C.uint(flags), C.int(1))
	return operrno("mdb_env_set_flags", ret)
}

// UnsetFlags clears flags in the environment.
//
// See mdb_env_set_flags.
func (env *Env) UnsetFlags(flags uint) error {
	ret := C.mdb_env_set_flags(env._env, C.uint(flags), C.int(0))
	return operrno("mdb_env_set_flags", ret)
}

// Flags returns the flags set in the environment.
//
// See mdb_env_get_flags.
func (env *Env) Flags() (uint, error) {
	var _flags C.uint
	ret := C.mdb_env_get_flags(env._env, &_flags)
	if ret != success {
		return 0, operrno("mdb_env_get_flags", ret)
	}
	return uint(_flags), nil
}

// Path returns the path argument passed to Open.  Path returns a non-nil error
// if env.Open() was not previously called.
//
// See mdb_env_get_path.
func (env *Env) Path() (string, error) {
	var cpath *C.char
	ret := C.mdb_env_get_path(env._env, &cpath)
	if ret != success {
		return "", operrno("mdb_env_get_path", ret)
	}
	if cpath == nil {
		return "", fmt.Errorf("env not open")
	}
	return C.GoString(cpath), nil
}

// SetMapSize sets the size of the environment memory map.
//
// See mdb_env_set_map_size.
func (env *Env) SetMapSize(size int64) error {
	if size < 0 {
		return fmt.Errorf("negative size")
	}
	ret := C.mdb_env_set_mapsize(env._env, C.size_t(size))
	return operrno("mdb_env_set_mapsize", ret)
}

// SetMaxReaders sets the maximum number of reader slots in the environment.
//
// See mdb_env_set_max_readers.
func (env *Env) SetMaxReaders(size int) error {
	if size < 0 {
		return fmt.Errorf("negative size")
	}
	ret := C.mdb_env_set_maxreaders(env._env, C.uint(size))
	return operrno("mdb_env_set_maxreaders", ret)
}

// MaxKeySize returns the maximum allowed length for a key.
//
// See mdb_env_get_maxkeysize.
func (env *Env) MaxKeySize() int {
	if env == nil {
		return int(C.mdb_env_get_maxkeysize(nil))
	}
	return int(C.mdb_env_get_maxkeysize(env._env))
}

// SetMaxDBs sets the maximum number of named databases for the environment.
//
// See mdb_env_set_maxdbs.
func (env *Env) SetMaxDBs(size int) error {
	if size < 0 {
		return fmt.Errorf("negative size")
	}
	ret := C.mdb_env_set_maxdbs(env._env, C.MDB_dbi(size))
	return operrno("mdb_env_set_maxdbs", ret)
}

// BeginTxn is a low-level (potentially dangerous) method to initialize a new
// transaction on env.  BeginTxn does not attempt to serialize operations on
// write transactions to the same OS thread and without care its use for write
// transactions can cause undefined results.
//
// Instead of BeginTxn users should call the View, Update, RunTxn methods.
//
// See mdb_txn_begin.
func (env *Env) BeginTxn(parent *Txn, flags uint) (*Txn, error) {
	return beginTxn(env, parent, flags)
}

// Run creates a new Txn and calls fn with it as an argument.  Run commits the
// transaction if fn returns nil otherwise the transaction is aborted.
//
// Because Run terminates the transaction goroutines should not retain
// references to it after fn returns.  Writable transactions (without the
// Readonly flag) must not be used from any goroutines other than the one
// running fn.
//
// See mdb_txn_begin.
func (env *Env) RunTxn(flags uint, fn TxnOp) error {
	if isReadonly(flags) {
		return env.run(flags, fn)
	}
	return env.runUpdate(flags, fn)
}

// View creates a readonly transaction with a consistent view of the
// environment and passes it to fn.  View terminates its transaction after fn
// returns.  Any error encountered by View is returned.
//
// Any call to Commit, Abort, Reset or Renew on a Txn created by View will
// panic.
func (env *Env) View(fn TxnOp) error {
	return env.run(Readonly, fn)
}

// Update creates a writable transaction and passes it to fn.  Update commits
// the transaction if fn returns without error otherwise Update aborts the
// transaction and returns the error.
//
// The Txn passed to fn must not be used from multiple goroutines, even with
// synchronization.
//
// Any call to Commit, Abort, Reset or Renew on a Txn created by Update will
// panic.
func (env *Env) Update(fn TxnOp) error {
	return env.runUpdate(0, fn)
}

func (env *Env) run(flags uint, fn TxnOp) error {
	txn, err := env.BeginTxn(nil, flags)
	if err != nil {
		return err
	}
	txn.managed = true
	defer txn.abort()
	err = fn(txn)
	if err != nil {
		return err
	}
	return txn.commit()
}

// runUpdate calls fn with a managed transaction on another goroutine.  The
// transaction runs inside a goroutine so thread locking does not interact
// negatively with other objects which would like to lock their thread.
func (env *Env) runUpdate(flags uint, fn TxnOp) error {
	errc := make(chan error)
	go func() {
		defer close(errc)
		runtime.LockOSThread()
		defer runtime.UnlockOSThread()
		err := env.run(flags, fn)
		if err != nil {
			errc <- err
		}
	}()
	return <-errc
}

// CloseDBI closes the database handle, db.  Normally calling CloseDBI
// explicitly is not necessary.
//
// It is the caller's responsibility to serialize calls to CloseDBI.
//
// See mdb_dbi_close.
func (env *Env) CloseDBI(db DBI) {
	C.mdb_dbi_close(env._env, C.MDB_dbi(db))
}

func isReadonly(flags uint) bool {
	return flags&Readonly != 0
}
<commit_msg>add missing bindings for mdb_reader_check and mdb_env_get_maxreaders<commit_after>package lmdb

/*
#include <stdlib.h>
#include <stdio.h>
#include "lmdb.h"
*/
import "C"

import (
	"errors"
	"fmt"
	"os"
	"runtime"
	"unsafe"
)

// success is a value returned from the LMDB API to indicate a successful call.
// The functions in this API this behavior and its use is not required.
const success = C.MDB_SUCCESS

const (
	// Flags for Env.Open.
	//
	// See mdb_env_open
	FixedMap    = C.MDB_FIXEDMAP   // Danger zone. Map memory at a fixed address.
	NoSubdir    = C.MDB_NOSUBDIR   // Argument to Open is a file, not a directory.
	Readonly    = C.MDB_RDONLY     // Used in several functions to denote an object as readonly.
	WriteMap    = C.MDB_WRITEMAP   // Use a writable memory map.
	NoMetaSync  = C.MDB_NOMETASYNC // Don't fsync metapage after commit.
	NoSync      = C.MDB_NOSYNC     // Don't fsync after commit.
	MapAsync    = C.MDB_MAPASYNC   // Flush asynchronously when using the WriteMap flag.
	NoTLS       = C.MDB_NOTLS      // Danger zone. When unset reader locktable slots are tied to their thread.
	NoLock      = C.MDB_NOLOCK     // Danger zone. LMDB does not use any locks.
	NoReadahead = C.MDB_NORDAHEAD  // Disable readahead. Requires OS support.
	NoMemInit   = C.MDB_NOMEMINIT  // Disable LMDB memory initialization.
)

// DBI is a handle for a database in an Env.
//
// See MDB_dbi
type DBI C.MDB_dbi

// Env is opaque structure for a database environment.  A DB environment
// supports multiple databases, all residing in the same shared-memory map.
//
// See MDB_env.
type Env struct {
	_env *C.MDB_env
}

// NewEnv allocates and initializes a new Env.
//
// See mdb_env_create.
func NewEnv() (*Env, error) {
	var _env *C.MDB_env
	ret := C.mdb_env_create(&_env)
	if ret != success {
		return nil, operrno("mdb_env_create", ret)
	}
	return &Env{_env}, nil
}

// Open an environment handle. If this function fails Close() must be called to
// discard the Env handle.  Open passes flags|NoTLS to mdb_env_open.
//
// See mdb_env_open.
func (env *Env) Open(path string, flags uint, mode os.FileMode) error {
	cpath := C.CString(path)
	defer C.free(unsafe.Pointer(cpath))
	ret := C.mdb_env_open(env._env, cpath, C.uint(NoTLS|flags), C.mdb_mode_t(mode))
	return operrno("mdb_env_open", ret)
}

// ReaderCheck clears stale entries from the reader lock table and returns the
// number of entries cleared.
//
// See mdb_reader_check()
func (env *Env) ReaderCheck() (int, error) {
	var _dead C.int
	ret := C.mdb_reader_check(env._env, &_dead)
	return int(_dead), operrno("mdb_reader_check", ret)
}

// Close shuts down the environment and releases the memory map.
//
// See mdb_env_close.
func (env *Env) Close() error {
	if env._env == nil {
		return errors.New("Environment already closed")
	}
	C.mdb_env_close(env._env)
	env._env = nil
	return nil
}

// Copy copies the data in env to an environment at path.
//
// See mdb_env_copy.
func (env *Env) Copy(path string) error {
	cpath := C.CString(path)
	defer C.free(unsafe.Pointer(cpath))
	ret := C.mdb_env_copy(env._env, cpath)
	return operrno("mdb_env_copy", ret)
}

// CopyFlag copies the data in env to an environment at path created with flags.
//
// See mdb_env_copy2.
func (env *Env) CopyFlag(path string, flags uint) error {
	cpath := C.CString(path)
	defer C.free(unsafe.Pointer(cpath))
	ret := C.mdb_env_copy2(env._env, cpath, C.uint(flags))
	return operrno("mdb_env_copy2", ret)
}

// Statistics for a database in the environment
//
// See MDB_stat.
type Stat struct {
	PSize         uint   // Size of a database page. This is currently the same for all databases.
	Depth         uint   // Depth (height) of the B-tree
	BranchPages   uint64 // Number of internal (non-leaf) pages
	LeafPages     uint64 // Number of leaf pages
	OverflowPages uint64 // Number of overflow pages
	Entries       uint64 // Number of data items
}

// Stat returns statistics about the environment.
//
// See mdb_env_stat.
func (env *Env) Stat() (*Stat, error) {
	var _stat C.MDB_stat
	ret := C.mdb_env_stat(env._env, &_stat)
	if ret != success {
		return nil, operrno("mdb_env_stat", ret)
	}
	stat := Stat{PSize: uint(_stat.ms_psize),
		Depth:         uint(_stat.ms_depth),
		BranchPages:   uint64(_stat.ms_branch_pages),
		LeafPages:     uint64(_stat.ms_leaf_pages),
		OverflowPages: uint64(_stat.ms_overflow_pages),
		Entries:       uint64(_stat.ms_entries)}
	return &stat, nil
}

// Information about the environment.
//
// See MDB_envinfo.
type EnvInfo struct {
	MapSize    int64 // Size of the data memory map
	LastPNO    int64 // ID of the last used page
	LastTxnID  int64 // ID of the last committed transaction
	MaxReaders uint  // maximum number of threads for the environment
	NumReaders uint  // maximum number of threads used in the environment
}

// Info returns information about the environment.
//
// See mdb_env_info.
func (env *Env) Info() (*EnvInfo, error) {
	var _info C.MDB_envinfo
	ret := C.mdb_env_info(env._env, &_info)
	if ret != success {
		return nil, operrno("mdb_env_info", ret)
	}
	info := EnvInfo{
		MapSize:    int64(_info.me_mapsize),
		LastPNO:    int64(_info.me_last_pgno),
		LastTxnID:  int64(_info.me_last_txnid),
		MaxReaders: uint(_info.me_maxreaders),
		NumReaders: uint(_info.me_numreaders),
	}
	return &info, nil
}

// Sync flushes buffers to disk.  If force is true a synchronous flush occurs
// and ignores any NoSync or MapAsync flag on the environment.
//
// See mdb_env_sync.
func (env *Env) Sync(force bool) error {
	ret := C.mdb_env_sync(env._env, cbool(force))
	return operrno("mdb_env_sync", ret)
}

// SetFlags sets flags in the environment.
//
// See mdb_env_set_flags.
func (env *Env) SetFlags(flags uint) error {
	ret := C.mdb_env_set_flags(env._env, C.uint(flags), C.int(1))
	return operrno("mdb_env_set_flags", ret)
}

// UnsetFlags clears flags in the environment.
//
// See mdb_env_set_flags.
func (env *Env) UnsetFlags(flags uint) error {
	ret := C.mdb_env_set_flags(env._env, C.uint(flags), C.int(0))
	return operrno("mdb_env_set_flags", ret)
}

// Flags returns the flags set in the environment.
//
// See mdb_env_get_flags.
func (env *Env) Flags() (uint, error) {
	var _flags C.uint
	ret := C.mdb_env_get_flags(env._env, &_flags)
	if ret != success {
		return 0, operrno("mdb_env_get_flags", ret)
	}
	return uint(_flags), nil
}

// Path returns the path argument passed to Open.  Path returns a non-nil error
// if env.Open() was not previously called.
//
// See mdb_env_get_path.
func (env *Env) Path() (string, error) {
	var cpath *C.char
	ret := C.mdb_env_get_path(env._env, &cpath)
	if ret != success {
		return "", operrno("mdb_env_get_path", ret)
	}
	if cpath == nil {
		return "", fmt.Errorf("env not open")
	}
	return C.GoString(cpath), nil
}

// SetMapSize sets the size of the environment memory map.
//
// See mdb_env_set_mapsize.
func (env *Env) SetMapSize(size int64) error {
	if size < 0 {
		return fmt.Errorf("negative size")
	}
	ret := C.mdb_env_set_mapsize(env._env, C.size_t(size))
	return operrno("mdb_env_set_mapsize", ret)
}

// SetMaxReaders sets the maximum number of reader slots in the environment.
//
// See mdb_env_set_maxreaders.
func (env *Env) SetMaxReaders(size int) error {
	if size < 0 {
		return fmt.Errorf("negative size")
	}
	ret := C.mdb_env_set_maxreaders(env._env, C.uint(size))
	return operrno("mdb_env_set_maxreaders", ret)
}

// MaxReaders returns the maximum number of reader slots for the environment.
//
// See mdb_env_get_maxreaders.
func (env *Env) MaxReaders() (int, error) {
	var max C.uint
	ret := C.mdb_env_get_maxreaders(env._env, &max)
	return int(max), operrno("mdb_env_get_maxreaders", ret)
}

// MaxKeySize returns the maximum allowed length for a key.
//
// See mdb_env_get_maxkeysize.
func (env *Env) MaxKeySize() int {
	if env == nil {
		return int(C.mdb_env_get_maxkeysize(nil))
	}
	return int(C.mdb_env_get_maxkeysize(env._env))
}

// SetMaxDBs sets the maximum number of named databases for the environment.
//
// See mdb_env_set_maxdbs.
func (env *Env) SetMaxDBs(size int) error {
	if size < 0 {
		return fmt.Errorf("negative size")
	}
	ret := C.mdb_env_set_maxdbs(env._env, C.MDB_dbi(size))
	return operrno("mdb_env_set_maxdbs", ret)
}

// BeginTxn is a low-level (potentially dangerous) method to initialize a new
// transaction on env.  BeginTxn does not attempt to serialize operations on
// write transactions to the same OS thread and without care its use for write
// transactions can cause undefined results.
//
// Instead of BeginTxn users should call the View, Update, RunTxn methods.
//
// See mdb_txn_begin.
func (env *Env) BeginTxn(parent *Txn, flags uint) (*Txn, error) {
	return beginTxn(env, parent, flags)
}

// Run creates a new Txn and calls fn with it as an argument.  Run commits the
// transaction if fn returns nil otherwise the transaction is aborted.
//
// Because Run terminates the transaction goroutines should not retain
// references to it after fn returns.  Writable transactions (without the
// Readonly flag) must not be used from any goroutines other than the one
// running fn.
//
// See mdb_txn_begin.
func (env *Env) RunTxn(flags uint, fn TxnOp) error {
	if isReadonly(flags) {
		return env.run(flags, fn)
	}
	return env.runUpdate(flags, fn)
}

// View creates a readonly transaction with a consistent view of the
// environment and passes it to fn.  View terminates its transaction after fn
// returns.  Any error encountered by View is returned.
//
// Any call to Commit, Abort, Reset or Renew on a Txn created by View will
// panic.
func (env *Env) View(fn TxnOp) error {
	return env.run(Readonly, fn)
}

// Update creates a writable transaction and passes it to fn.  Update commits
// the transaction if fn returns without error otherwise Update aborts the
// transaction and returns the error.
//
// The Txn passed to fn must not be used from multiple goroutines, even with
// synchronization.
//
// Any call to Commit, Abort, Reset or Renew on a Txn created by Update will
// panic.
func (env *Env) Update(fn TxnOp) error {
	return env.runUpdate(0, fn)
}

func (env *Env) run(flags uint, fn TxnOp) error {
	txn, err := env.BeginTxn(nil, flags)
	if err != nil {
		return err
	}
	txn.managed = true
	defer txn.abort()
	err = fn(txn)
	if err != nil {
		return err
	}
	return txn.commit()
}

// runUpdate calls fn with a managed transaction on another goroutine.  The
// transaction runs inside a goroutine so thread locking does not interact
// negatively with other objects which would like to lock their thread.
func (env *Env) runUpdate(flags uint, fn TxnOp) error {
	errc := make(chan error)
	go func() {
		defer close(errc)
		runtime.LockOSThread()
		defer runtime.UnlockOSThread()
		err := env.run(flags, fn)
		if err != nil {
			errc <- err
		}
	}()
	return <-errc
}

// CloseDBI closes the database handle, db.  Normally calling CloseDBI
// explicitly is not necessary.
//
// It is the caller's responsibility to serialize calls to CloseDBI.
//
// See mdb_dbi_close.
func (env *Env) CloseDBI(db DBI) {
	C.mdb_dbi_close(env._env, C.MDB_dbi(db))
}

func isReadonly(flags uint) bool {
	return flags&Readonly != 0
}
<|endoftext|>