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

// Package agentlib implements a client for communicating with an agentd process
// holding the private key for an identity.
package agentlib

import (
	"fmt"
	"net"
	"os"
	"strconv"
	"syscall"

	"v.io/v23/context"
	"v.io/v23/naming"
	"v.io/v23/options"
	"v.io/v23/rpc"
	"v.io/v23/security"
	"v.io/v23/verror"
	"v.io/v23/vtrace"
	"v.io/x/lib/vlog"
	"v.io/x/ref/services/agent/internal/cache"
	"v.io/x/ref/services/agent/internal/unixfd"
)

const pkgPath = "v.io/x/ref/services/agent/agentlib"

// Errors
var (
	errInvalidProtocol = verror.Register(pkgPath+".errInvalidProtocol",
		verror.NoRetry, "{1:}{2:} invalid agent protocol {3}")
)

type client struct {
	caller caller
	key    security.PublicKey
}

type caller struct {
	ctx    *context.T
	client rpc.Client
	name   string
}

func (c *caller) call(name string, results []interface{}, args ...interface{}) error {
	call, err := c.startCall(name, args...)
	if err != nil {
		return err
	}
	if err := call.Finish(results...); err != nil {
		return err
	}
	return nil
}

func (c *caller) startCall(name string, args ...interface{}) (rpc.ClientCall, error) {
	ctx, _ := vtrace.WithNewTrace(c.ctx)
	// SecurityNone is safe here since we're using anonymous unix sockets.
	return c.client.StartCall(ctx, c.name, name, args, options.SecurityNone, options.NoResolve{})
}

func results(inputs ...interface{}) []interface{} {
	return inputs
}

// NewAgentPrincipal returns a security.Pricipal using the PrivateKey held in a remote agent process.
// 'endpoint' is the endpoint for connecting to the agent, typically obtained from
// os.GetEnv(envvar.AgentEndpoint).
// 'ctx' should not have a deadline, and should never be cancelled while the
// principal is in use.
func NewAgentPrincipal(ctx *context.T, endpoint naming.Endpoint, insecureClient rpc.Client) (security.Principal, error) {
	p, err := newUncachedPrincipal(ctx, endpoint, insecureClient)
	if err != nil {
		return p, err
	}
	call, callErr := p.caller.startCall("NotifyWhenChanged")
	if callErr != nil {
		return nil, callErr
	}
	return cache.NewCachedPrincipal(p.caller.ctx, p, call)
}
func newUncachedPrincipal(ctx *context.T, ep naming.Endpoint, insecureClient rpc.Client) (*client, error) {
	// This isn't a real vanadium endpoint. It contains the vanadium version
	// info, but the address is serving the agent protocol.
	if ep.Addr().Network() != "" {
		return nil, verror.New(errInvalidProtocol, ctx, ep.Addr().Network())
	}
	fd, err := strconv.Atoi(ep.Addr().String())
	if err != nil {
		return nil, err
	}
	syscall.ForkLock.Lock()
	fd, err = syscall.Dup(fd)
	if err == nil {
		syscall.CloseOnExec(fd)
	}
	syscall.ForkLock.Unlock()
	if err != nil {
		return nil, err
	}
	f := os.NewFile(uintptr(fd), "agent_client")
	defer f.Close()
	conn, err := net.FileConn(f)
	if err != nil {
		return nil, err
	}
	// This is just an arbitrary 1 byte string. The value is ignored.
	data := make([]byte, 1)
	addr, err := unixfd.SendConnection(conn.(*net.UnixConn), data)
	if err != nil {
		return nil, err
	}
	caller := caller{
		client: insecureClient,
		name:   naming.JoinAddressName(agentEndpoint("unixfd", addr.String()), ""),
		ctx:    ctx,
	}
	agent := &client{caller: caller}
	if err := agent.fetchPublicKey(); err != nil {
		return nil, err
	}
	return agent, nil
}

func (c *client) fetchPublicKey() (err error) {
	var b []byte
	if err = c.caller.call("PublicKey", results(&b)); err != nil {
		return
	}
	c.key, err = security.UnmarshalPublicKey(b)
	return
}

func (c *client) Bless(key security.PublicKey, with security.Blessings, extension string, caveat security.Caveat, additionalCaveats ...security.Caveat) (security.Blessings, error) {
	var blessings security.Blessings
	marshalledKey, err := key.MarshalBinary()
	if err != nil {
		return security.Blessings{}, err
	}
	err = c.caller.call("Bless", results(&blessings), marshalledKey, with, extension, caveat, additionalCaveats)
	return blessings, err
}

func (c *client) BlessSelf(name string, caveats ...security.Caveat) (security.Blessings, error) {
	var blessings security.Blessings
	err := c.caller.call("BlessSelf", results(&blessings), name, caveats)
	return blessings, err
}

func (c *client) Sign(message []byte) (sig security.Signature, err error) {
	err = c.caller.call("Sign", results(&sig), message)
	return
}

func (c *client) MintDischarge(forCaveat, caveatOnDischarge security.Caveat, additionalCaveatsOnDischarge ...security.Caveat) (security.Discharge, error) {
	var discharge security.Discharge
	if err := c.caller.call("MintDischarge", results(&discharge), forCaveat, caveatOnDischarge, additionalCaveatsOnDischarge); err != nil {
		return security.Discharge{}, err
	}
	return discharge, nil
}

func (c *client) PublicKey() security.PublicKey {
	return c.key
}

func (c *client) BlessingsByName(pattern security.BlessingPattern) []security.Blessings {
	var blessings []security.Blessings
	if err := c.caller.call("BlessingsByName", results(&blessings), pattern); err != nil {
		vlog.Errorf("error calling BlessingsByName: %v", err)
		return nil
	}
	return blessings
}

func (c *client) BlessingsInfo(blessings security.Blessings) map[string][]security.Caveat {
	var bInfo map[string][]security.Caveat
	err := c.caller.call("BlessingsInfo", results(&bInfo), blessings)
	if err != nil {
		vlog.Errorf("error calling BlessingsInfo: %v", err)
		return nil
	}
	return bInfo
}
func (c *client) BlessingStore() security.BlessingStore {
	return &blessingStore{c.caller, c.key}
}

func (c *client) Roots() security.BlessingRoots {
	return &blessingRoots{c.caller}
}

func (c *client) AddToRoots(blessings security.Blessings) error {
	return c.caller.call("AddToRoots", results(), blessings)
}

type blessingStore struct {
	caller caller
	key    security.PublicKey
}

func (b *blessingStore) Set(blessings security.Blessings, forPeers security.BlessingPattern) (security.Blessings, error) {
	var previous security.Blessings
	err := b.caller.call("BlessingStoreSet", results(&previous), blessings, forPeers)
	return previous, err
}

func (b *blessingStore) ForPeer(peerBlessings ...string) security.Blessings {
	var blessings security.Blessings
	if err := b.caller.call("BlessingStoreForPeer", results(&blessings), peerBlessings); err != nil {
		vlog.Errorf("error calling BlessingStorePeerBlessings: %v", err)
	}
	return blessings
}

func (b *blessingStore) SetDefault(blessings security.Blessings) error {
	return b.caller.call("BlessingStoreSetDefault", results(), blessings)
}

func (b *blessingStore) Default() security.Blessings {
	var blessings security.Blessings
	err := b.caller.call("BlessingStoreDefault", results(&blessings))
	if err != nil {
		vlog.Errorf("error calling BlessingStoreDefault: %v", err)
		return security.Blessings{}
	}
	return blessings
}

func (b *blessingStore) PublicKey() security.PublicKey {
	return b.key
}

func (b *blessingStore) PeerBlessings() map[security.BlessingPattern]security.Blessings {
	var bmap map[security.BlessingPattern]security.Blessings
	err := b.caller.call("BlessingStorePeerBlessings", results(&bmap))
	if err != nil {
		vlog.Errorf("error calling BlessingStorePeerBlessings: %v", err)
		return nil
	}
	return bmap
}

func (b *blessingStore) DebugString() (s string) {
	err := b.caller.call("BlessingStoreDebugString", results(&s))
	if err != nil {
		s = fmt.Sprintf("error calling BlessingStoreDebugString: %v", err)
		vlog.Errorf(s)
	}
	return
}

func (b *blessingStore) CacheDischarge(d security.Discharge, c security.Caveat, i security.DischargeImpetus) {
	err := b.caller.call("BlessingStoreCacheDischarge", results(), d, c, i)
	if err != nil {
		vlog.Errorf("error calling BlessingStoreCacheDischarge: %v", err)
	}
}

func (b *blessingStore) ClearDischarges(discharges ...security.Discharge) {
	err := b.caller.call("BlessingStoreClearDischarges", results(), discharges)
	if err != nil {
		vlog.Errorf("error calling BlessingStoreClearDischarges: %v", err)
	}
}

func (b *blessingStore) Discharge(caveat security.Caveat, impetus security.DischargeImpetus) (out security.Discharge) {
	err := b.caller.call("BlessingStoreDischarge", results(&out), caveat, impetus)
	if err != nil {
		vlog.Errorf("error calling BlessingStoreDischarge: %v", err)
	}
	return
}

type blessingRoots struct {
	caller caller
}

func (b *blessingRoots) Add(root security.PublicKey, pattern security.BlessingPattern) error {
	marshalledKey, err := root.MarshalBinary()
	if err != nil {
		return err
	}
	return b.caller.call("BlessingRootsAdd", results(), marshalledKey, pattern)
}

func (b *blessingRoots) Recognized(root security.PublicKey, blessing string) error {
	marshalledKey, err := root.MarshalBinary()
	if err != nil {
		return err
	}
	return b.caller.call("BlessingRootsRecognized", results(), marshalledKey, blessing)
}

func (b *blessingRoots) Dump() map[security.BlessingPattern][]security.PublicKey {
	var marshaledRoots map[security.BlessingPattern][][]byte
	if err := b.caller.call("BlessingRootsDump", results(&marshaledRoots)); err != nil {
		vlog.Errorf("error calling BlessingRootsDump: %v", err)
		return nil
	}
	ret := make(map[security.BlessingPattern][]security.PublicKey)
	for p, marshaledKeys := range marshaledRoots {
		for _, marshaledKey := range marshaledKeys {
			key, err := security.UnmarshalPublicKey(marshaledKey)
			if err != nil {
				vlog.Errorf("security.UnmarshalPublicKey(%v) returned error: %v", marshaledKey, err)
				continue
			}
			ret[p] = append(ret[p], key)
		}
	}
	return ret
}

func (b *blessingRoots) DebugString() (s string) {
	err := b.caller.call("BlessingRootsDebugString", results(&s))
	if err != nil {
		s = fmt.Sprintf("error calling BlessingRootsDebugString: %v", err)
		vlog.Errorf(s)
	}
	return
}

func agentEndpoint(proto, addr string) string {
	// TODO: use naming.FormatEndpoint when it supports version 5.
	return fmt.Sprintf("@5@%s@%s@@s@@@", proto, addr)
}

func AgentEndpoint(fd int) string {
	// We use an empty protocol here because this isn't really speaking
	// veyron rpc.
	return agentEndpoint("", fmt.Sprintf("%d", fd))
}
<commit_msg>ref/services/agent: Change errorf logs to infof since we currently do not act on this errors.<commit_after>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package agentlib implements a client for communicating with an agentd process
// holding the private key for an identity.
package agentlib

import (
	"fmt"
	"net"
	"os"
	"strconv"
	"syscall"

	"v.io/v23/context"
	"v.io/v23/naming"
	"v.io/v23/options"
	"v.io/v23/rpc"
	"v.io/v23/security"
	"v.io/v23/verror"
	"v.io/v23/vtrace"
	"v.io/x/lib/vlog"
	"v.io/x/ref/services/agent/internal/cache"
	"v.io/x/ref/services/agent/internal/unixfd"
)

const pkgPath = "v.io/x/ref/services/agent/agentlib"

// Errors
var (
	errInvalidProtocol = verror.Register(pkgPath+".errInvalidProtocol",
		verror.NoRetry, "{1:}{2:} invalid agent protocol {3}")
)

type client struct {
	caller caller
	key    security.PublicKey
}

type caller struct {
	ctx    *context.T
	client rpc.Client
	name   string
}

func (c *caller) call(name string, results []interface{}, args ...interface{}) error {
	call, err := c.startCall(name, args...)
	if err != nil {
		return err
	}
	if err := call.Finish(results...); err != nil {
		return err
	}
	return nil
}

func (c *caller) startCall(name string, args ...interface{}) (rpc.ClientCall, error) {
	ctx, _ := vtrace.WithNewTrace(c.ctx)
	// SecurityNone is safe here since we're using anonymous unix sockets.
	return c.client.StartCall(ctx, c.name, name, args, options.SecurityNone, options.NoResolve{})
}

func results(inputs ...interface{}) []interface{} {
	return inputs
}

// NewAgentPrincipal returns a security.Pricipal using the PrivateKey held in a remote agent process.
// 'endpoint' is the endpoint for connecting to the agent, typically obtained from
// os.GetEnv(envvar.AgentEndpoint).
// 'ctx' should not have a deadline, and should never be cancelled while the
// principal is in use.
func NewAgentPrincipal(ctx *context.T, endpoint naming.Endpoint, insecureClient rpc.Client) (security.Principal, error) {
	p, err := newUncachedPrincipal(ctx, endpoint, insecureClient)
	if err != nil {
		return p, err
	}
	call, callErr := p.caller.startCall("NotifyWhenChanged")
	if callErr != nil {
		return nil, callErr
	}
	return cache.NewCachedPrincipal(p.caller.ctx, p, call)
}
func newUncachedPrincipal(ctx *context.T, ep naming.Endpoint, insecureClient rpc.Client) (*client, error) {
	// This isn't a real vanadium endpoint. It contains the vanadium version
	// info, but the address is serving the agent protocol.
	if ep.Addr().Network() != "" {
		return nil, verror.New(errInvalidProtocol, ctx, ep.Addr().Network())
	}
	fd, err := strconv.Atoi(ep.Addr().String())
	if err != nil {
		return nil, err
	}
	syscall.ForkLock.Lock()
	fd, err = syscall.Dup(fd)
	if err == nil {
		syscall.CloseOnExec(fd)
	}
	syscall.ForkLock.Unlock()
	if err != nil {
		return nil, err
	}
	f := os.NewFile(uintptr(fd), "agent_client")
	defer f.Close()
	conn, err := net.FileConn(f)
	if err != nil {
		return nil, err
	}
	// This is just an arbitrary 1 byte string. The value is ignored.
	data := make([]byte, 1)
	addr, err := unixfd.SendConnection(conn.(*net.UnixConn), data)
	if err != nil {
		return nil, err
	}
	caller := caller{
		client: insecureClient,
		name:   naming.JoinAddressName(agentEndpoint("unixfd", addr.String()), ""),
		ctx:    ctx,
	}
	agent := &client{caller: caller}
	if err := agent.fetchPublicKey(); err != nil {
		return nil, err
	}
	return agent, nil
}

func (c *client) fetchPublicKey() (err error) {
	var b []byte
	if err = c.caller.call("PublicKey", results(&b)); err != nil {
		return
	}
	c.key, err = security.UnmarshalPublicKey(b)
	return
}

func (c *client) Bless(key security.PublicKey, with security.Blessings, extension string, caveat security.Caveat, additionalCaveats ...security.Caveat) (security.Blessings, error) {
	var blessings security.Blessings
	marshalledKey, err := key.MarshalBinary()
	if err != nil {
		return security.Blessings{}, err
	}
	err = c.caller.call("Bless", results(&blessings), marshalledKey, with, extension, caveat, additionalCaveats)
	return blessings, err
}

func (c *client) BlessSelf(name string, caveats ...security.Caveat) (security.Blessings, error) {
	var blessings security.Blessings
	err := c.caller.call("BlessSelf", results(&blessings), name, caveats)
	return blessings, err
}

func (c *client) Sign(message []byte) (sig security.Signature, err error) {
	err = c.caller.call("Sign", results(&sig), message)
	return
}

func (c *client) MintDischarge(forCaveat, caveatOnDischarge security.Caveat, additionalCaveatsOnDischarge ...security.Caveat) (security.Discharge, error) {
	var discharge security.Discharge
	if err := c.caller.call("MintDischarge", results(&discharge), forCaveat, caveatOnDischarge, additionalCaveatsOnDischarge); err != nil {
		return security.Discharge{}, err
	}
	return discharge, nil
}

func (c *client) PublicKey() security.PublicKey {
	return c.key
}

func (c *client) BlessingsByName(pattern security.BlessingPattern) []security.Blessings {
	var blessings []security.Blessings
	if err := c.caller.call("BlessingsByName", results(&blessings), pattern); err != nil {
		vlog.Infof("error calling BlessingsByName: %v", err)
		return nil
	}
	return blessings
}

func (c *client) BlessingsInfo(blessings security.Blessings) map[string][]security.Caveat {
	var bInfo map[string][]security.Caveat
	err := c.caller.call("BlessingsInfo", results(&bInfo), blessings)
	if err != nil {
		vlog.Infof("error calling BlessingsInfo: %v", err)
		return nil
	}
	return bInfo
}
func (c *client) BlessingStore() security.BlessingStore {
	return &blessingStore{c.caller, c.key}
}

func (c *client) Roots() security.BlessingRoots {
	return &blessingRoots{c.caller}
}

func (c *client) AddToRoots(blessings security.Blessings) error {
	return c.caller.call("AddToRoots", results(), blessings)
}

type blessingStore struct {
	caller caller
	key    security.PublicKey
}

func (b *blessingStore) Set(blessings security.Blessings, forPeers security.BlessingPattern) (security.Blessings, error) {
	var previous security.Blessings
	err := b.caller.call("BlessingStoreSet", results(&previous), blessings, forPeers)
	return previous, err
}

func (b *blessingStore) ForPeer(peerBlessings ...string) security.Blessings {
	var blessings security.Blessings
	if err := b.caller.call("BlessingStoreForPeer", results(&blessings), peerBlessings); err != nil {
		vlog.Infof("error calling BlessingStorePeerBlessings: %v", err)
	}
	return blessings
}

func (b *blessingStore) SetDefault(blessings security.Blessings) error {
	return b.caller.call("BlessingStoreSetDefault", results(), blessings)
}

func (b *blessingStore) Default() security.Blessings {
	var blessings security.Blessings
	err := b.caller.call("BlessingStoreDefault", results(&blessings))
	if err != nil {
		vlog.Infof("error calling BlessingStoreDefault: %v", err)
		return security.Blessings{}
	}
	return blessings
}

func (b *blessingStore) PublicKey() security.PublicKey {
	return b.key
}

func (b *blessingStore) PeerBlessings() map[security.BlessingPattern]security.Blessings {
	var bmap map[security.BlessingPattern]security.Blessings
	err := b.caller.call("BlessingStorePeerBlessings", results(&bmap))
	if err != nil {
		vlog.Infof("error calling BlessingStorePeerBlessings: %v", err)
		return nil
	}
	return bmap
}

func (b *blessingStore) DebugString() (s string) {
	err := b.caller.call("BlessingStoreDebugString", results(&s))
	if err != nil {
		s = fmt.Sprintf("error calling BlessingStoreDebugString: %v", err)
		vlog.Infof(s)
	}
	return
}

func (b *blessingStore) CacheDischarge(d security.Discharge, c security.Caveat, i security.DischargeImpetus) {
	err := b.caller.call("BlessingStoreCacheDischarge", results(), d, c, i)
	if err != nil {
		vlog.Infof("error calling BlessingStoreCacheDischarge: %v", err)
	}
}

func (b *blessingStore) ClearDischarges(discharges ...security.Discharge) {
	err := b.caller.call("BlessingStoreClearDischarges", results(), discharges)
	if err != nil {
		vlog.Infof("error calling BlessingStoreClearDischarges: %v", err)
	}
}

func (b *blessingStore) Discharge(caveat security.Caveat, impetus security.DischargeImpetus) (out security.Discharge) {
	err := b.caller.call("BlessingStoreDischarge", results(&out), caveat, impetus)
	if err != nil {
		vlog.Infof("error calling BlessingStoreDischarge: %v", err)
	}
	return
}

type blessingRoots struct {
	caller caller
}

func (b *blessingRoots) Add(root security.PublicKey, pattern security.BlessingPattern) error {
	marshalledKey, err := root.MarshalBinary()
	if err != nil {
		return err
	}
	return b.caller.call("BlessingRootsAdd", results(), marshalledKey, pattern)
}

func (b *blessingRoots) Recognized(root security.PublicKey, blessing string) error {
	marshalledKey, err := root.MarshalBinary()
	if err != nil {
		return err
	}
	return b.caller.call("BlessingRootsRecognized", results(), marshalledKey, blessing)
}

func (b *blessingRoots) Dump() map[security.BlessingPattern][]security.PublicKey {
	var marshaledRoots map[security.BlessingPattern][][]byte
	if err := b.caller.call("BlessingRootsDump", results(&marshaledRoots)); err != nil {
		vlog.Infof("error calling BlessingRootsDump: %v", err)
		return nil
	}
	ret := make(map[security.BlessingPattern][]security.PublicKey)
	for p, marshaledKeys := range marshaledRoots {
		for _, marshaledKey := range marshaledKeys {
			key, err := security.UnmarshalPublicKey(marshaledKey)
			if err != nil {
				vlog.Infof("security.UnmarshalPublicKey(%v) returned error: %v", marshaledKey, err)
				continue
			}
			ret[p] = append(ret[p], key)
		}
	}
	return ret
}

func (b *blessingRoots) DebugString() (s string) {
	err := b.caller.call("BlessingRootsDebugString", results(&s))
	if err != nil {
		s = fmt.Sprintf("error calling BlessingRootsDebugString: %v", err)
		vlog.Infof(s)
	}
	return
}

func agentEndpoint(proto, addr string) string {
	// TODO: use naming.FormatEndpoint when it supports version 5.
	return fmt.Sprintf("@5@%s@%s@@s@@@", proto, addr)
}

func AgentEndpoint(fd int) string {
	// We use an empty protocol here because this isn't really speaking
	// veyron rpc.
	return agentEndpoint("", fmt.Sprintf("%d", fd))
}
<|endoftext|>


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

package hdiutil

import (
	"bytes"
	"os/exec"
	"strconv"
)

// AttachFlag implements a hdiutil attach command flag interface.
type AttachFlag interface {
	attachFlag() string
}

type rwType int

const (
	// Readonly force the resulting device to be read-only.
	readonly rwType = 1 << iota
	// Readwrite attempt to override the DiskImages framework's decision to attach a particular image read-only.
	// For example, -readwrite can be used to modify the HFS+ filesystem on a HFS+/ISO hybrid CD image.
	readwrite
)

func (rw rwType) attachFlag() string {
	switch rw {
	case readonly:
		return "-readonly"
	case readwrite:
		return "-readwrite"
	default:
		return ""
	}
}

// kernel attempt to attach this image without a helper process; fail if unsupported.
// Only UDRW, UDRO, UDZO, ULFO, and UDSP images are supported in-kernel.
// Encryption and HTTP are supported by the kernel driver.
// -nokernel attach with a helper process. This is (again) the default as of Mac OS X 10.5.
type kernel bool

func (k kernel) attachFlag() string { return boolNoFlag(bool(k), "kernel") }

// notremovable prevent this image from being detached. Only root can use this option.
// A reboot is necessary to cleanly detach an image attached with -notremovable.
type notRemovable bool

func (n notRemovable) attachFlag() string { return boolFlag(bool(n), "notremovable") }

// mount indicate whether filesystems in the image should be mounted or not.
// The default is required (attach will fail if no filesystems mount).
type mount string

func (m mount) attachFlag() string { return stringFlag(string(m), "mount") }

type noMount bool

func (n noMount) attachFlag() string { return boolFlag(bool(n), "nomount") }

// MountRoot mount volumes on subdirectories of path instead of under /Volumes. path must exist.
// Full mount point paths must be less than MNAMELEN characters (increased from 90 to 1024 in Mac OS X 10.6).
type MountRoot string

func (m MountRoot) attachFlag() string { return stringFlag(string(m), "mountroot") }

// MountRandom like -mountroot, but mount point directory names are randomized with mkdtemp(3).
type MountRandom string

func (m MountRandom) attachFlag() string { return stringFlag(string(m), "mountrandom") }

// MountPoint assuming only one volume, mount it at path instead of in /Volumes.
// See fstab(5) for ways a system administrator can make particular volumes automatically mount in particular filesystem locations by editing the file /etc/fstab.
type MountPoint string

func (m MountPoint) attachFlag() string { return stringFlag(string(m), "mountpoint") }

// noBrowse render any volumes invisible in applications such as the macOS Finder.
type noBrowse bool

func (n noBrowse) attachFlag() string { return boolFlag(bool(n), "nobrowse") }

// owners specify that owners on any filesystems be honored or not.
type owners string

const (
	// OwnersOn is -owners on
	ownersOn owners = "on"
	// OwnersOff is -owners off
	ownersOff owners = "off"
)

func (o owners) attachFlag() string { return stringFlag(string(o), "owners") }

// drivekey specify a key/value pair to be set on the device in the IOKit registry.
type drivekey struct {
	Key   string
	Value string
}

// section attach a subsection of a disk image.
// subspec is any of <offset>, <first-last>, or <start,count> in 0-based sectors.
// Ranges are inclusive.
type section [2]int

func (s section) attachFlag() string {
	var arg string
	for v := range s {
		arg = arg + strconv.Itoa(v)
	}
	return stringFlag(arg, "section")
}

// verify do [not] verify the image.
// By default, hdiutil attach attempts to intelligently verify images that contain checksums before attaching them.
// If hdiutil can write to an image it has verified, attach will store an attribute with
// the image so that it will not be verified again unless its timestamp changes.
// To maintain backwards compatibility, hdid(8) does not attempt to verify images before attaching them.
// Preferences keys: skip-verify, skip-verify-remote, skip-verify-locked, skip-previously-verified
type verify bool

func (v verify) attachFlag() string { return boolNoFlag(bool(v), "verify") }

// ignoreBadChecksums specify whether bad checksums should be ignored.
// The default is to abort when a bad checksum is detected.
// Preferences key: ignore-bad-checksums
type ignoreBadChecksums bool

func (i ignoreBadChecksums) attachFlag() string { return boolNoFlag(bool(i), "ignoreBadChecksums") }

// idme do perform IDME actions on IDME images.
// IDME actions are not performed by default.
// Preferences key: skip-idme
type idme bool

func (i idme) attachFlag() string { return boolNoFlag(bool(i), "idme") }

// idmeReveal do [not] reveal (in the Finder) the results of IDME processing.
// Preferences key: skip-idme-reveal
type idmeReveal bool

func (i idmeReveal) attachFlag() string { return boolNoFlag(bool(i), "idmereveal") }

// idmeTrash do [not] put IDME images in the trash after processing.
// Preferences key: skip-idme-trash
type idmeTrash bool

func (i idmeTrash) attachFlag() string { return boolNoFlag(bool(i), "idmetrash") }

// autoOpen do [not] auto-open volumes (in the Finder) after attaching an image.
// By default, double-clicking a read-only disk image causes the resulting volume to be opened in the Finder.
// hdiutil defaults to -noautoopen.
type autoOpen bool

func (a autoOpen) attachFlag() string { return boolNoFlag(bool(a), "autoopen") }

// autoOpenRO do [not] auto-open read-only volumes.
// Preferences key: auto-open-ro-root
type autoOpenRO bool

func (a autoOpenRO) attachFlag() string { return boolNoFlag(bool(a), "autoopenro") }

// autoOpenRW do [not] auto-open read/write volumes.
// Preferences key: auto-open-rw-root
type autoOpenRW bool

func (a autoOpenRW) attachFlag() string { return boolNoFlag(bool(a), "autoopenrw") }

// autoFsck do [not] force automatic file system checking before mounting a disk image.
// By default, only quarantined images (e.g. downloaded from the Internet) that have not previously passed fsck are checked.
// Preferences key: auto-fsck
type autoFsck bool

func (a autoFsck) attachFlag() string { return boolNoFlag(bool(a), "autofsck") }

const (
	AttachReadonly             rwType             = readonly
	AttachReadWrite            rwType             = readwrite
	AttachKernel               kernel             = true
	AttachNoKernel             kernel             = false
	AttachNotRemovable         notRemovable       = true
	AttachMountRequired        mount              = "required"
	AttachMountOptional        mount              = "optional"
	AttachMountSuppressed      mount              = "suppressed"
	AttachNoMount              noMount            = true
	AttachNoBrowse             noBrowse           = true
	AttachOwnersOn             owners             = ownersOn
	AttachOwnersOff            owners             = ownersOff
	AttachVerify               verify             = true
	AttachNoVerify             verify             = false
	AttachIgnoreBadChecksums   ignoreBadChecksums = true
	AttachNoIgnoreBadChecksums ignoreBadChecksums = false
	AttachIdme                 idme               = true
	AttachNoIdme               idme               = false
	AttachIdmeReveal           idmeReveal         = true
	AttachNoIdmeReveal         idmeReveal         = false
	AttachIdmeTrash            idmeTrash          = true
	AttachNoIdmeTrash          idmeTrash          = false
	AttachAutoOpen             autoOpen           = true
	AttachNoAutoOpen           autoOpen           = false
	AttachAutoOpenRO           autoOpenRO         = true
	AttachNoAutoOpenRO         autoOpenRO         = false
	AttachAutoOpenRW           autoOpenRW         = true
	AttachNoAutoOpenRW         autoOpenRW         = false
	AttachAutoFsck             autoFsck           = true
	AttachNoAutoFsck           autoFsck           = false
)

// Attach attach the image file. The returns device node path and error.
func Attach(image string, flags ...AttachFlag) (string, error) {
	cmd := exec.Command(hdiutilPath, "attach", image)

	if len(flags) != 0 {
		args := []string{}
		for _, f := range flags {
			args = append(args, f.attachFlag())
		}
		cmd.Args = append(cmd.Args, args...)
	}

	out, err := cmd.Output()
	if err != nil {
		return "", err
	}

	return string(bytes.TrimSpace(out)), nil
}
<commit_msg>attach: add godoc comment<commit_after>// Copyright 2017 The go-darwin Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package hdiutil

import (
	"bytes"
	"os/exec"
	"strconv"
)

// AttachFlag implements a hdiutil attach command flag interface.
type AttachFlag interface {
	attachFlag() string
}

type rwType int

const (
	// Readonly force the resulting device to be read-only.
	readonly rwType = 1 << iota
	// Readwrite attempt to override the DiskImages framework's decision to attach a particular image read-only.
	// For example, -readwrite can be used to modify the HFS+ filesystem on a HFS+/ISO hybrid CD image.
	readwrite
)

func (rw rwType) attachFlag() string {
	switch rw {
	case readonly:
		return "-readonly"
	case readwrite:
		return "-readwrite"
	default:
		return ""
	}
}

// kernel attempt to attach this image without a helper process; fail if unsupported.
// Only UDRW, UDRO, UDZO, ULFO, and UDSP images are supported in-kernel.
// Encryption and HTTP are supported by the kernel driver.
// -nokernel attach with a helper process. This is (again) the default as of Mac OS X 10.5.
type kernel bool

func (k kernel) attachFlag() string { return boolNoFlag(bool(k), "kernel") }

// notremovable prevent this image from being detached. Only root can use this option.
// A reboot is necessary to cleanly detach an image attached with -notremovable.
type notRemovable bool

func (n notRemovable) attachFlag() string { return boolFlag(bool(n), "notremovable") }

// mount indicate whether filesystems in the image should be mounted or not.
// The default is required (attach will fail if no filesystems mount).
type mount string

func (m mount) attachFlag() string { return stringFlag(string(m), "mount") }

type noMount bool

func (n noMount) attachFlag() string { return boolFlag(bool(n), "nomount") }

// MountRoot mount volumes on subdirectories of path instead of under /Volumes. path must exist.
// Full mount point paths must be less than MNAMELEN characters (increased from 90 to 1024 in Mac OS X 10.6).
type MountRoot string

func (m MountRoot) attachFlag() string { return stringFlag(string(m), "mountroot") }

// MountRandom like -mountroot, but mount point directory names are randomized with mkdtemp(3).
type MountRandom string

func (m MountRandom) attachFlag() string { return stringFlag(string(m), "mountrandom") }

// MountPoint assuming only one volume, mount it at path instead of in /Volumes.
// See fstab(5) for ways a system administrator can make particular volumes automatically mount in particular filesystem locations by editing the file /etc/fstab.
type MountPoint string

func (m MountPoint) attachFlag() string { return stringFlag(string(m), "mountpoint") }

// noBrowse render any volumes invisible in applications such as the macOS Finder.
type noBrowse bool

func (n noBrowse) attachFlag() string { return boolFlag(bool(n), "nobrowse") }

// owners specify that owners on any filesystems be honored or not.
type owners string

const (
	// OwnersOn is -owners on
	ownersOn owners = "on"
	// OwnersOff is -owners off
	ownersOff owners = "off"
)

func (o owners) attachFlag() string { return stringFlag(string(o), "owners") }

// drivekey specify a key/value pair to be set on the device in the IOKit registry.
type drivekey struct {
	Key   string
	Value string
}

// section attach a subsection of a disk image.
// subspec is any of <offset>, <first-last>, or <start,count> in 0-based sectors.
// Ranges are inclusive.
type section [2]int

func (s section) attachFlag() string {
	var arg string
	for v := range s {
		arg = arg + strconv.Itoa(v)
	}
	return stringFlag(arg, "section")
}

// verify do [not] verify the image.
// By default, hdiutil attach attempts to intelligently verify images that contain checksums before attaching them.
// If hdiutil can write to an image it has verified, attach will store an attribute with
// the image so that it will not be verified again unless its timestamp changes.
// To maintain backwards compatibility, hdid(8) does not attempt to verify images before attaching them.
// Preferences keys: skip-verify, skip-verify-remote, skip-verify-locked, skip-previously-verified
type verify bool

func (v verify) attachFlag() string { return boolNoFlag(bool(v), "verify") }

// ignoreBadChecksums specify whether bad checksums should be ignored.
// The default is to abort when a bad checksum is detected.
// Preferences key: ignore-bad-checksums
type ignoreBadChecksums bool

func (i ignoreBadChecksums) attachFlag() string { return boolNoFlag(bool(i), "ignoreBadChecksums") }

// idme do perform IDME actions on IDME images.
// IDME actions are not performed by default.
// Preferences key: skip-idme
type idme bool

func (i idme) attachFlag() string { return boolNoFlag(bool(i), "idme") }

// idmeReveal do [not] reveal (in the Finder) the results of IDME processing.
// Preferences key: skip-idme-reveal
type idmeReveal bool

func (i idmeReveal) attachFlag() string { return boolNoFlag(bool(i), "idmereveal") }

// idmeTrash do [not] put IDME images in the trash after processing.
// Preferences key: skip-idme-trash
type idmeTrash bool

func (i idmeTrash) attachFlag() string { return boolNoFlag(bool(i), "idmetrash") }

// autoOpen do [not] auto-open volumes (in the Finder) after attaching an image.
// By default, double-clicking a read-only disk image causes the resulting volume to be opened in the Finder.
// hdiutil defaults to -noautoopen.
type autoOpen bool

func (a autoOpen) attachFlag() string { return boolNoFlag(bool(a), "autoopen") }

// autoOpenRO do [not] auto-open read-only volumes.
// Preferences key: auto-open-ro-root
type autoOpenRO bool

func (a autoOpenRO) attachFlag() string { return boolNoFlag(bool(a), "autoopenro") }

// autoOpenRW do [not] auto-open read/write volumes.
// Preferences key: auto-open-rw-root
type autoOpenRW bool

func (a autoOpenRW) attachFlag() string { return boolNoFlag(bool(a), "autoopenrw") }

// autoFsck do [not] force automatic file system checking before mounting a disk image.
// By default, only quarantined images (e.g. downloaded from the Internet) that have not previously passed fsck are checked.
// Preferences key: auto-fsck
type autoFsck bool

func (a autoFsck) attachFlag() string { return boolNoFlag(bool(a), "autofsck") }

const (
	// AttachReadonly force the resulting device to be read-only.
	AttachReadonly rwType = readonly

	// AttachReadWrite attempt to override the DiskImages framework's decision to attach a particular image read-only.
	//
	// For example, -readwrite can be used to modify the HFS+ filesystem on a HFS+/ISO hybrid CD image.
	AttachReadWrite rwType = readwrite

	// AttachKernel attempt to attach this image without a helper process; fail if unsupported.
	//
	// Only UDRW, UDRO, UDZO, ULFO, and UDSP images are supported in-kernel. Encryption and HTTP are supported by the kernel driver.
	AttachKernel kernel = true

	// AttachNoKernel attach with a helper process.  This is (again) the default as of Mac OS X 10.5.
	AttachNoKernel kernel = false

	// AttachNotRemovable prevent this image from being detached. Only root can use this option.
	//
	// A reboot is necessary to cleanly detach an image attached with -notremovable.
	AttachNotRemovable notRemovable = true

	// AttachMountRequired indicate to -mount required.
	AttachMountRequired mount = "required"

	// AttachMountOptional indicate to -mount optional.
	AttachMountOptional mount = "optional"

	// AttachMountSuppressed indicate to -mount suppressed.
	AttachMountSuppressed mount = "suppressed"

	// AttachNoMount identical to -mount suppressed
	AttachNoMount noMount = true

	// AttachNoBrowse render any volumes invisible in applications such as the macOS Finder.
	AttachNoBrowse noBrowse = true

	// AttachOwnersOn owners on any filesystems be honored.
	AttachOwnersOn owners = ownersOn

	// AttachOwnersOff owners on any filesystems be not honored.
	AttachOwnersOff owners = ownersOff

	// AttachVerify do verify the image.
	AttachVerify verify = true

	// AttachNoVerify do not verify the image.
	AttachNoVerify verify = false

	// AttachIgnoreBadChecksums bad checksums should be ignored.
	AttachIgnoreBadChecksums ignoreBadChecksums = true

	// AttachNoIgnoreBadChecksums bad checksums should be not ignored.
	AttachNoIgnoreBadChecksums ignoreBadChecksums = false

	// AttachIdme do perform IDME actions on IDME images.
	AttachIdme idme = true

	// AttachNoIdme do not perform IDME actions on IDME images.
	AttachNoIdme idme = false

	// AttachIdmeReveal do reveal (in the Finder) the results of IDME processing.
	AttachIdmeReveal idmeReveal = true

	// AttachNoIdmeReveal do not reveal (in the Finder) the results of IDME processing.
	AttachNoIdmeReveal idmeReveal = false

	// AttachIdmeTrash do put IDME images in the trash after processing.
	AttachIdmeTrash idmeTrash = true
	// AttachNoIdmeTrash do not put IDME images in the trash after processing.
	AttachNoIdmeTrash idmeTrash = false

	// AttachAutoOpen do not auto-open volumes (in the Finder) after attaching an image.
	AttachAutoOpen autoOpen = true

	// AttachNoAutoOpen do not auto-open volumes (in the Finder) after attaching an image.
	AttachNoAutoOpen autoOpen = false

	// AttachAutoOpenRO do auto-open read-only volumes.
	AttachAutoOpenRO autoOpenRO = true

	// AttachNoAutoOpenRO do not auto-open read-only volumes.
	AttachNoAutoOpenRO autoOpenRO = false

	// AttachAutoOpenRW do auto-open read/write volumes.
	AttachAutoOpenRW autoOpenRW = true

	// AttachNoAutoOpenRW do not auto-open read/write volumes.
	AttachNoAutoOpenRW autoOpenRW = false

	// AttachAutoFsck do force automatic file system checking before mounting a disk image.
	AttachAutoFsck autoFsck = true

	// AttachNoAutoFsck do not force automatic file system checking before mounting a disk image.
	AttachNoAutoFsck autoFsck = false
)

// Attach attach the image file. The returns device node path and error.
func Attach(image string, flags ...AttachFlag) (string, error) {
	cmd := exec.Command(hdiutilPath, "attach", image)

	if len(flags) != 0 {
		args := []string{}
		for _, f := range flags {
			args = append(args, f.attachFlag())
		}
		cmd.Args = append(cmd.Args, args...)
	}

	out, err := cmd.Output()
	if err != nil {
		return "", err
	}

	return string(bytes.TrimSpace(out)), nil
}
<|endoftext|>


<commit_before>package memberships

import (
	"encoding/json"

	"time"

	"github.com/Financial-Times/neo-cypher-runner-go/neocypherrunner"
	"github.com/Financial-Times/neo-utils-go/neoutils"
	"github.com/jmcvetta/neoism"
)

type CypherDriver struct {
	cypherRunner neocypherrunner.CypherRunner
	indexManager neoutils.IndexManager
}

func NewCypherDriver(cypherRunner neocypherrunner.CypherRunner, indexManager neoutils.IndexManager) CypherDriver {
	return CypherDriver{cypherRunner, indexManager}
}

func (mcd CypherDriver) Initialise() error {
	return neoutils.EnsureConstraints(mcd.indexManager, map[string]string{
		"Thing":      "uuid",
		"Concept":    "uuid",
		"Membership": "uuid"})
}

func (mcd CypherDriver) Read(uuid string) (interface{}, bool, error) {
	results := []membership{}

	query := &neoism.CypherQuery{
		Statement: `
		MATCH (m:Membership {uuid:{uuid}})-[:HAS_ORGANISATION]->(o:Thing)
					OPTIONAL MATCH (p:Thing)<-[:HAS_MEMBER]-(m)
					OPTIONAL MATCH (r:Thing)<-[rr:HAS_ROLE]-(m)
					WITH p, m, o, collect({roleuuid:r.uuid,inceptionDate:rr.inceptionDate,terminationDate:rr.terminationDate }) as membershipRoles
					WITH p, m, o, membershipRoles, collect({authority:'http://api.ft.com/system/FACTSET', identifierValue: m.factsetIdentifier})as identifiers
					return m.uuid as uuid , m.prefLabel as prefLabel,m.inceptionDate as inceptionDate,
					m.terminationDate as terminationDate, o.uuid as organisationUuid, p.uuid as personUuid,membershipRoles,identifiers
					`,

		Parameters: map[string]interface{}{
			"uuid": uuid,
		},
		Result: &results,
	}
	err := mcd.cypherRunner.CypherBatch([]*neoism.CypherQuery{query})

	if err != nil {
		return membership{}, false, err
	}

	if len(results) == 0 {
		return membership{}, false, nil
	}

	result := results[0]

	//TODO fix query to not retun a role of empty fields when there are no roles
	if len(result.Identifiers) == 1 && (result.Identifiers[0].IdentifierValue == "") {
		result.Identifiers = make([]identifier, 0, 0)
	}

	if len(result.MembershipRoles) == 1 && (result.MembershipRoles[0].RoleUUID == "") {
		result.MembershipRoles = make([]role, 0, 0)
	}

	return result, true, nil
}

func (mcd CypherDriver) Write(thing interface{}) error {
	m := thing.(membership)

	params := map[string]interface{}{
		"uuid": m.UUID,
	}

	if m.PrefLabel != "" {
		params["prefLabel"] = m.PrefLabel
	}

	if m.InceptionDate != "" {
		addDateToQueryParams(params, "inceptionDate", m.InceptionDate)
	}

	if m.TerminationDate != "" {
		addDateToQueryParams(params, "terminationDate", m.TerminationDate)
	}

	for _, identifier := range m.Identifiers {
		if identifier.Authority == fsAuthority {
			params["factsetIdentifier"] = identifier.IdentifierValue
		}
	}
	if len(m.Identifiers) == 0 {
		m.Identifiers = make([]identifier, 0, 0)
	}

	queryDelEntitiessRel := &neoism.CypherQuery{
		Statement: `MATCH (m:Thing {uuid: {uuid}})
					OPTIONAL MATCH (p:Thing)<-[rm:HAS_MEMBER]-(m)
					OPTIONAL MATCH (o:Thing)<-[ro:HAS_ORGANISATION]-(m)
					DELETE rm, ro
		`,
		Parameters: map[string]interface{}{
			"uuid": m.UUID,
		},
	}

	queries := []*neoism.CypherQuery{queryDelEntitiessRel}

	query := &neoism.CypherQuery{
		Statement: `MERGE (m:Thing {uuid: {uuid}})
					MERGE (p:Thing {uuid: {personuuid}})
					MERGE (o:Thing {uuid: {organisationuuid}})
					CREATE(m)-[:HAS_MEMBER]->(p)
		            CREATE (m)-[:HAS_ORGANISATION]->(o)
					set m={allprops}
					set m :Concept
					set m :Membership
		`,
		Parameters: map[string]interface{}{
			"uuid":             m.UUID,
			"allprops":         params,
			"personuuid":       m.PersonUUID,
			"organisationuuid": m.OrganisationUUID,
		},
	}

	queries = append(queries, query)

	queryDelRolesRel := &neoism.CypherQuery{
		Statement: `MATCH (m:Thing {uuid: {uuid}})
					OPTIONAL MATCH (r:Thing)<-[rr:HAS_ROLE]-(m)
					DELETE  rr
		`,
		Parameters: map[string]interface{}{
			"uuid": m.UUID,
		},
	}
	queries = append(queries, queryDelRolesRel)

	for _, mr := range m.MembershipRoles {
		rrparams := make(map[string]interface{})

		if mr.InceptionDate != "" {
			addDateToQueryParams(rrparams, "inceptionDate", mr.InceptionDate)
		}

		if mr.TerminationDate != "" {
			addDateToQueryParams(rrparams, "terminationDate", mr.TerminationDate)
		}

		query := &neoism.CypherQuery{
			Statement: `
				MERGE (m:Thing {uuid:{muuid}})
				MERGE (r:Thing {uuid:{ruuid}})
				CREATE (m)-[rel:HAS_ROLE]->(r)
				SET rel={rrparams}
			`,
			Parameters: map[string]interface{}{
				"muuid":    m.UUID,
				"ruuid":    mr.RoleUUID,
				"rrparams": rrparams,
			},
		}

		queries = append(queries, query)
	}
	return mcd.cypherRunner.CypherBatch(queries)
}

func (mcd CypherDriver) Delete(uuid string) (bool, error) {
	clearNode := &neoism.CypherQuery{
		Statement: `
				MATCH (m:Thing {uuid: {uuid}})
				OPTIONAL MATCH (m)-[prel:HAS_MEMBER]->(p:Thing)
				OPTIONAL MATCH (m)-[orel:HAS_ORGANISATION]->(o:Thing)
				OPTIONAL MATCH (r:Thing)<-[rrel:HAS_ROLE]-(m)
				REMOVE m:Concept
				REMOVE m:Membership
				SET m={props}
				DElETE rrel, orel, prel
		`,
		Parameters: map[string]interface{}{
			"uuid": uuid,
			"props": map[string]interface{}{
				"uuid": uuid,
			},
		},

		IncludeStats: true,
	}

	removeNodeIfUnused := &neoism.CypherQuery{
		Statement: `
				MATCH (m:Thing {uuid: {uuid}})
				OPTIONAL MATCH (m)-[a]-(x)
				WITH m, count(a) AS relCount
				WHERE relCount = 0
				DELETE m
			`,
		Parameters: map[string]interface{}{
			"uuid": uuid,
		},
	}

	err := mcd.cypherRunner.CypherBatch([]*neoism.CypherQuery{clearNode, removeNodeIfUnused})

	s1, err := clearNode.Stats()
	if err != nil {
		return false, err
	}

	var deleted bool
	if s1.ContainsUpdates && s1.LabelsRemoved > 0 {
		deleted = true
	}

	return deleted, err
}

func (pcd CypherDriver) DecodeJSON(dec *json.Decoder) (interface{}, string, error) {
	m := membership{}
	err := dec.Decode(&m)
	return m, m.UUID, err

}

func (pcd CypherDriver) Check() error {
	return neoutils.Check(pcd.cypherRunner)
}

func (pcd CypherDriver) Count() (int, error) {

	results := []struct {
		Count int `json:"c"`
	}{}

	query := &neoism.CypherQuery{
		Statement: `MATCH (n:Membership) return count(n) as c`,
		Result:    &results,
	}

	err := pcd.cypherRunner.CypherBatch([]*neoism.CypherQuery{query})

	if err != nil {
		return 0, err
	}

	return results[0].Count, nil
}

func addDateToQueryParams(params map[string]interface{}, dateName string, dateVal string) error {
	params[dateName] = dateVal
	datetimeEpoch, err := time.Parse(time.RFC3339, dateVal)
	if err != nil {
		return err
	}
	params[dateName+"Epoch"] = datetimeEpoch.Unix()
	return nil
}

const (
	fsAuthority = "http://api.ft.com/system/FACTSET"
)
<commit_msg>REmoved reference to cypher runner<commit_after>package memberships

import (
	"encoding/json"

	"time"

	"github.com/Financial-Times/neo-cypher-runner-go/neocypherrunner"
	"github.com/Financial-Times/neo-utils-go/neoutils"
	"github.com/jmcvetta/neoism"
)

type CypherDriver struct {
	cypherRunner neocypherrunner.CypherRunner
	indexManager neoutils.IndexManager
}

func NewCypherDriver(cypherRunner neoutils.CypherRunner, indexManager neoutils.IndexManager) CypherDriver {
	return CypherDriver{cypherRunner, indexManager}
}

func (mcd CypherDriver) Initialise() error {
	return neoutils.EnsureConstraints(mcd.indexManager, map[string]string{
		"Thing":      "uuid",
		"Concept":    "uuid",
		"Membership": "uuid"})
}

func (mcd CypherDriver) Read(uuid string) (interface{}, bool, error) {
	results := []membership{}

	query := &neoism.CypherQuery{
		Statement: `
		MATCH (m:Membership {uuid:{uuid}})-[:HAS_ORGANISATION]->(o:Thing)
					OPTIONAL MATCH (p:Thing)<-[:HAS_MEMBER]-(m)
					OPTIONAL MATCH (r:Thing)<-[rr:HAS_ROLE]-(m)
					WITH p, m, o, collect({roleuuid:r.uuid,inceptionDate:rr.inceptionDate,terminationDate:rr.terminationDate }) as membershipRoles
					WITH p, m, o, membershipRoles, collect({authority:'http://api.ft.com/system/FACTSET', identifierValue: m.factsetIdentifier})as identifiers
					return m.uuid as uuid , m.prefLabel as prefLabel,m.inceptionDate as inceptionDate,
					m.terminationDate as terminationDate, o.uuid as organisationUuid, p.uuid as personUuid,membershipRoles,identifiers
					`,

		Parameters: map[string]interface{}{
			"uuid": uuid,
		},
		Result: &results,
	}
	err := mcd.cypherRunner.CypherBatch([]*neoism.CypherQuery{query})

	if err != nil {
		return membership{}, false, err
	}

	if len(results) == 0 {
		return membership{}, false, nil
	}

	result := results[0]

	//TODO fix query to not retun a role of empty fields when there are no roles
	if len(result.Identifiers) == 1 && (result.Identifiers[0].IdentifierValue == "") {
		result.Identifiers = make([]identifier, 0, 0)
	}

	if len(result.MembershipRoles) == 1 && (result.MembershipRoles[0].RoleUUID == "") {
		result.MembershipRoles = make([]role, 0, 0)
	}

	return result, true, nil
}

func (mcd CypherDriver) Write(thing interface{}) error {
	m := thing.(membership)

	params := map[string]interface{}{
		"uuid": m.UUID,
	}

	if m.PrefLabel != "" {
		params["prefLabel"] = m.PrefLabel
	}

	if m.InceptionDate != "" {
		addDateToQueryParams(params, "inceptionDate", m.InceptionDate)
	}

	if m.TerminationDate != "" {
		addDateToQueryParams(params, "terminationDate", m.TerminationDate)
	}

	for _, identifier := range m.Identifiers {
		if identifier.Authority == fsAuthority {
			params["factsetIdentifier"] = identifier.IdentifierValue
		}
	}
	if len(m.Identifiers) == 0 {
		m.Identifiers = make([]identifier, 0, 0)
	}

	queryDelEntitiessRel := &neoism.CypherQuery{
		Statement: `MATCH (m:Thing {uuid: {uuid}})
					OPTIONAL MATCH (p:Thing)<-[rm:HAS_MEMBER]-(m)
					OPTIONAL MATCH (o:Thing)<-[ro:HAS_ORGANISATION]-(m)
					DELETE rm, ro
		`,
		Parameters: map[string]interface{}{
			"uuid": m.UUID,
		},
	}

	queries := []*neoism.CypherQuery{queryDelEntitiessRel}

	query := &neoism.CypherQuery{
		Statement: `MERGE (m:Thing {uuid: {uuid}})
					MERGE (p:Thing {uuid: {personuuid}})
					MERGE (o:Thing {uuid: {organisationuuid}})
					CREATE(m)-[:HAS_MEMBER]->(p)
		            CREATE (m)-[:HAS_ORGANISATION]->(o)
					set m={allprops}
					set m :Concept
					set m :Membership
		`,
		Parameters: map[string]interface{}{
			"uuid":             m.UUID,
			"allprops":         params,
			"personuuid":       m.PersonUUID,
			"organisationuuid": m.OrganisationUUID,
		},
	}

	queries = append(queries, query)

	queryDelRolesRel := &neoism.CypherQuery{
		Statement: `MATCH (m:Thing {uuid: {uuid}})
					OPTIONAL MATCH (r:Thing)<-[rr:HAS_ROLE]-(m)
					DELETE  rr
		`,
		Parameters: map[string]interface{}{
			"uuid": m.UUID,
		},
	}
	queries = append(queries, queryDelRolesRel)

	for _, mr := range m.MembershipRoles {
		rrparams := make(map[string]interface{})

		if mr.InceptionDate != "" {
			addDateToQueryParams(rrparams, "inceptionDate", mr.InceptionDate)
		}

		if mr.TerminationDate != "" {
			addDateToQueryParams(rrparams, "terminationDate", mr.TerminationDate)
		}

		query := &neoism.CypherQuery{
			Statement: `
				MERGE (m:Thing {uuid:{muuid}})
				MERGE (r:Thing {uuid:{ruuid}})
				CREATE (m)-[rel:HAS_ROLE]->(r)
				SET rel={rrparams}
			`,
			Parameters: map[string]interface{}{
				"muuid":    m.UUID,
				"ruuid":    mr.RoleUUID,
				"rrparams": rrparams,
			},
		}

		queries = append(queries, query)
	}
	return mcd.cypherRunner.CypherBatch(queries)
}

func (mcd CypherDriver) Delete(uuid string) (bool, error) {
	clearNode := &neoism.CypherQuery{
		Statement: `
				MATCH (m:Thing {uuid: {uuid}})
				OPTIONAL MATCH (m)-[prel:HAS_MEMBER]->(p:Thing)
				OPTIONAL MATCH (m)-[orel:HAS_ORGANISATION]->(o:Thing)
				OPTIONAL MATCH (r:Thing)<-[rrel:HAS_ROLE]-(m)
				REMOVE m:Concept
				REMOVE m:Membership
				SET m={props}
				DElETE rrel, orel, prel
		`,
		Parameters: map[string]interface{}{
			"uuid": uuid,
			"props": map[string]interface{}{
				"uuid": uuid,
			},
		},

		IncludeStats: true,
	}

	removeNodeIfUnused := &neoism.CypherQuery{
		Statement: `
				MATCH (m:Thing {uuid: {uuid}})
				OPTIONAL MATCH (m)-[a]-(x)
				WITH m, count(a) AS relCount
				WHERE relCount = 0
				DELETE m
			`,
		Parameters: map[string]interface{}{
			"uuid": uuid,
		},
	}

	err := mcd.cypherRunner.CypherBatch([]*neoism.CypherQuery{clearNode, removeNodeIfUnused})

	s1, err := clearNode.Stats()
	if err != nil {
		return false, err
	}

	var deleted bool
	if s1.ContainsUpdates && s1.LabelsRemoved > 0 {
		deleted = true
	}

	return deleted, err
}

func (pcd CypherDriver) DecodeJSON(dec *json.Decoder) (interface{}, string, error) {
	m := membership{}
	err := dec.Decode(&m)
	return m, m.UUID, err

}

func (pcd CypherDriver) Check() error {
	return neoutils.Check(pcd.cypherRunner)
}

func (pcd CypherDriver) Count() (int, error) {

	results := []struct {
		Count int `json:"c"`
	}{}

	query := &neoism.CypherQuery{
		Statement: `MATCH (n:Membership) return count(n) as c`,
		Result:    &results,
	}

	err := pcd.cypherRunner.CypherBatch([]*neoism.CypherQuery{query})

	if err != nil {
		return 0, err
	}

	return results[0].Count, nil
}

func addDateToQueryParams(params map[string]interface{}, dateName string, dateVal string) error {
	params[dateName] = dateVal
	datetimeEpoch, err := time.Parse(time.RFC3339, dateVal)
	if err != nil {
		return err
	}
	params[dateName+"Epoch"] = datetimeEpoch.Unix()
	return nil
}

const (
	fsAuthority = "http://api.ft.com/system/FACTSET"
)
<|endoftext|>


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

// Package adler32 implements the Adler-32 checksum.
//
// It is defined in RFC 1950:
//	Adler-32 is composed of two sums accumulated per byte: s1 is
//	the sum of all bytes, s2 is the sum of all s1 values. Both sums
//	are done modulo 65521. s1 is initialized to 1, s2 to zero.  The
//	Adler-32 checksum is stored as s2*65536 + s1 in most-
//	significant-byte first (network) order.
package adler32

import "hash"

const (
	// mod is the largest prime that is less than 65536.
	mod = 65521
	// nmax is the largest n such that
	// 255 * n * (n+1) / 2 + (n+1) * (mod-1) <= 2^32-1.
	// It is mentioned in RFC 1950 (search for "5552").
	nmax = 5552
)

// The size of an Adler-32 checksum in bytes.
const Size = 4

// digest represents the partial evaluation of a checksum.
// The low 16 bits are s1, the high 16 bits are s2.
type digest uint32

func (d *digest) Reset() { *d = 1 }

// New returns a new hash.Hash32 computing the Adler-32 checksum.
// Its Sum method will lay the value out in big-endian byte order.
func New() hash.Hash32 {
	d := new(digest)
	d.Reset()
	return d
}

func (d *digest) Size() int { return Size }

func (d *digest) BlockSize() int { return 1 }

// Add p to the running checksum d.
func update(d digest, p []byte) digest {
	s1, s2 := uint32(d&0xffff), uint32(d>>16)
	for len(p) > 0 {
		var q []byte
		if len(p) > nmax {
			p, q = p[:nmax], p[nmax:]
		}
		for _, x := range p {
			s1 += uint32(x)
			s2 += s1
		}
		s1 %= mod
		s2 %= mod
		p = q
	}
	return digest(s2<<16 | s1)
}

func (d *digest) Write(p []byte) (nn int, err error) {
	*d = update(*d, p)
	return len(p), nil
}

func (d *digest) Sum32() uint32 { return uint32(*d) }

func (d *digest) Sum(in []byte) []byte {
	s := uint32(*d)
	return append(in, byte(s>>24), byte(s>>16), byte(s>>8), byte(s))
}

// Checksum returns the Adler-32 checksum of data.
func Checksum(data []byte) uint32 { return uint32(update(1, data)) }
<commit_msg>hash/adler32: Unroll loop for extra performance.<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package adler32 implements the Adler-32 checksum.
//
// It is defined in RFC 1950:
//	Adler-32 is composed of two sums accumulated per byte: s1 is
//	the sum of all bytes, s2 is the sum of all s1 values. Both sums
//	are done modulo 65521. s1 is initialized to 1, s2 to zero.  The
//	Adler-32 checksum is stored as s2*65536 + s1 in most-
//	significant-byte first (network) order.
package adler32

import "hash"

const (
	// mod is the largest prime that is less than 65536.
	mod = 65521
	// nmax is the largest n such that
	// 255 * n * (n+1) / 2 + (n+1) * (mod-1) <= 2^32-1.
	// It is mentioned in RFC 1950 (search for "5552").
	nmax = 5552
)

// The size of an Adler-32 checksum in bytes.
const Size = 4

// digest represents the partial evaluation of a checksum.
// The low 16 bits are s1, the high 16 bits are s2.
type digest uint32

func (d *digest) Reset() { *d = 1 }

// New returns a new hash.Hash32 computing the Adler-32 checksum.
// Its Sum method will lay the value out in big-endian byte order.
func New() hash.Hash32 {
	d := new(digest)
	d.Reset()
	return d
}

func (d *digest) Size() int { return Size }

func (d *digest) BlockSize() int { return 4 }

// Add p to the running checksum d.
func update(d digest, p []byte) digest {
	s1, s2 := uint32(d&0xffff), uint32(d>>16)
	for len(p) > 0 {
		var q []byte
		if len(p) > nmax {
			p, q = p[:nmax], p[nmax:]
		}
		for len(p) >= 4 {
			s1 += uint32(p[0])
			s2 += s1
			s1 += uint32(p[1])
			s2 += s1
			s1 += uint32(p[2])
			s2 += s1
			s1 += uint32(p[3])
			s2 += s1
			p = p[4:]
		}
		for _, x := range p {
			s1 += uint32(x)
			s2 += s1
		}
		s1 %= mod
		s2 %= mod
		p = q
	}
	return digest(s2<<16 | s1)
}

func (d *digest) Write(p []byte) (nn int, err error) {
	*d = update(*d, p)
	return len(p), nil
}

func (d *digest) Sum32() uint32 { return uint32(*d) }

func (d *digest) Sum(in []byte) []byte {
	s := uint32(*d)
	return append(in, byte(s>>24), byte(s>>16), byte(s>>8), byte(s))
}

// Checksum returns the Adler-32 checksum of data.
func Checksum(data []byte) uint32 { return uint32(update(1, data)) }
<|endoftext|>


<commit_before>package srpc

import (
	"bufio"
	"crypto/tls"
	"encoding/gob"
	"errors"
	"github.com/Symantec/Dominator/lib/x509util"
	"github.com/Symantec/tricorder/go/tricorder"
	"github.com/Symantec/tricorder/go/tricorder/units"
	"io"
	"log"
	"net"
	"net/http"
	"path/filepath"
	"reflect"
	"sort"
	"strings"
	"sync"
	"time"
)

const (
	connectString   = "200 Connected to Go SRPC"
	rpcPath         = "/_goSRPC_/"
	tlsRpcPath      = "/_go_TLS_SRPC_/"
	listMethodsPath = rpcPath + "listMethods"
)

type methodWrapper struct {
	plain                         bool
	fn                            reflect.Value
	requestType                   reflect.Type
	responseType                  reflect.Type
	failedCallsDistribution       *tricorder.CumulativeDistribution
	failedRRCallsDistribution     *tricorder.CumulativeDistribution
	numDeniedCalls                uint64
	numPermittedCalls             uint64
	successfulCallsDistribution   *tricorder.CumulativeDistribution
	successfulRRCallsDistribution *tricorder.CumulativeDistribution
}

type receiverType struct {
	methods map[string]*methodWrapper
}

var (
	receivers              map[string]receiverType = make(map[string]receiverType)
	serverMetricsDir       *tricorder.DirectorySpec
	bucketer               *tricorder.Bucketer
	mutex                  sync.Mutex
	numConnections         uint64
	numRejectedConnections uint64
)

// Precompute some reflect types. Can't use the types directly because Typeof
// takes an empty interface value. This is annoying.
var typeOfConn = reflect.TypeOf((**Conn)(nil)).Elem()
var typeOfError = reflect.TypeOf((*error)(nil)).Elem()

func init() {
	http.HandleFunc(rpcPath, unsecuredHttpHandler)
	http.HandleFunc(tlsRpcPath, tlsHttpHandler)
	http.HandleFunc(listMethodsPath, listMethodsHttpHandler)
	var err error
	serverMetricsDir, err = tricorder.RegisterDirectory("srpc/server")
	if err != nil {
		panic(err)
	}
	err = serverMetricsDir.RegisterMetric("num-connections", &numConnections,
		units.None, "number of connections")
	if err != nil {
		panic(err)
	}
	err = serverMetricsDir.RegisterMetric("num-rejected-connections",
		&numRejectedConnections, units.None, "number of rejected connections")
	if err != nil {
		panic(err)
	}
	bucketer = tricorder.NewGeometricBucketer(0.1, 1e5)
}

func registerName(name string, rcvr interface{}) error {
	var receiver receiverType
	receiver.methods = make(map[string]*methodWrapper)
	typeOfReceiver := reflect.TypeOf(rcvr)
	valueOfReceiver := reflect.ValueOf(rcvr)
	receiverMetricsDir, err := serverMetricsDir.RegisterDirectory(name)
	if err != nil {
		return err
	}
	for index := 0; index < typeOfReceiver.NumMethod(); index++ {
		method := typeOfReceiver.Method(index)
		if method.PkgPath != "" { // Method must be exported.
			continue
		}
		methodType := method.Type
		mVal := getMethod(methodType, valueOfReceiver.Method(index))
		if mVal == nil {
			continue
		}
		receiver.methods[method.Name] = mVal
		dir, err := receiverMetricsDir.RegisterDirectory(method.Name)
		if err != nil {
			return err
		}
		if err := mVal.registerMetrics(dir); err != nil {
			return err
		}
	}
	receivers[name] = receiver
	return nil
}

func getMethod(methodType reflect.Type, fn reflect.Value) *methodWrapper {
	if methodType.NumOut() != 1 {
		return nil
	}
	if methodType.Out(0) != typeOfError {
		return nil
	}
	if methodType.NumIn() == 2 {
		// Method needs two ins: receiver, *Conn.
		if methodType.In(1) != typeOfConn {
			return nil
		}
		return &methodWrapper{plain: true, fn: fn}
	}
	if methodType.NumIn() == 4 {
		// Method needs four ins: receiver, *Conn, request, *reply.
		if methodType.In(1) != typeOfConn {
			return nil
		}
		if methodType.In(3).Kind() != reflect.Ptr {
			return nil
		}
		return &methodWrapper{
			plain:        false,
			fn:           fn,
			requestType:  methodType.In(2),
			responseType: methodType.In(3).Elem()}
	}
	return nil
}

func (m *methodWrapper) registerMetrics(dir *tricorder.DirectorySpec) error {
	m.failedCallsDistribution = bucketer.NewCumulativeDistribution()
	err := dir.RegisterMetric("failed-call-durations",
		m.failedCallsDistribution, units.Millisecond,
		"duration of failed calls")
	if err != nil {
		return err
	}
	err = dir.RegisterMetric("num-denied-calls", &m.numDeniedCalls,
		units.None, "number of denied calls to method")
	if err != nil {
		return err
	}
	err = dir.RegisterMetric("num-permitted-calls", &m.numPermittedCalls,
		units.None, "number of permitted calls to method")
	if err != nil {
		return err
	}
	m.successfulCallsDistribution = bucketer.NewCumulativeDistribution()
	err = dir.RegisterMetric("successful-call-durations",
		m.successfulCallsDistribution, units.Millisecond,
		"duration of successful calls")
	if err != nil {
		return err
	}
	if m.plain {
		return nil
	}
	m.failedRRCallsDistribution = bucketer.NewCumulativeDistribution()
	err = dir.RegisterMetric("failed-request-reply-call-durations",
		m.failedRRCallsDistribution, units.Millisecond,
		"duration of failed request-reply calls")
	if err != nil {
		return err
	}
	m.successfulRRCallsDistribution = bucketer.NewCumulativeDistribution()
	err = dir.RegisterMetric("successful-request-reply-call-durations",
		m.successfulRRCallsDistribution, units.Millisecond,
		"duration of successful request-reply calls")
	if err != nil {
		return err
	}
	return nil
}

func unsecuredHttpHandler(w http.ResponseWriter, req *http.Request) {
	httpHandler(w, req, false)
}

func tlsHttpHandler(w http.ResponseWriter, req *http.Request) {
	httpHandler(w, req, true)
}

func httpHandler(w http.ResponseWriter, req *http.Request, doTls bool) {
	mutex.Lock()
	numConnections++
	mutex.Unlock()
	if doTls && serverTlsConfig == nil {
		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
		w.WriteHeader(http.StatusNotFound)
		return
	}
	if (tlsRequired && !doTls) || req.Method != "CONNECT" {
		mutex.Lock()
		numRejectedConnections++
		mutex.Unlock()
		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}
	unsecuredConn, bufrw, err := w.(http.Hijacker).Hijack()
	if err != nil {
		log.Println("rpc hijacking ", req.RemoteAddr, ": ", err.Error())
		return
	}
	if tcpConn, ok := unsecuredConn.(*net.TCPConn); ok {
		if err := tcpConn.SetKeepAlive(true); err != nil {
			log.Println("error setting keepalive: ", err.Error())
			return
		}
		if err := tcpConn.SetKeepAlivePeriod(time.Minute * 5); err != nil {
			log.Println("error setting keepalive period: ", err.Error())
			return
		}
	}
	_, err = io.WriteString(unsecuredConn, "HTTP/1.0 "+connectString+"\n\n")
	if err != nil {
		log.Println("error writing connect message: ", err.Error())
		return
	}
	myConn := new(Conn)
	if doTls {
		tlsConn := tls.Server(unsecuredConn, serverTlsConfig)
		defer tlsConn.Close()
		if err := tlsConn.Handshake(); err != nil {
			mutex.Lock()
			numRejectedConnections++
			mutex.Unlock()
			log.Println(err)
			return
		}
		myConn.isEncrypted = true
		myConn.username, myConn.permittedMethods, err = getAuth(
			tlsConn.ConnectionState())
		if err != nil {
			log.Println(err)
			return
		}
		myConn.ReadWriter = bufio.NewReadWriter(bufio.NewReader(tlsConn),
			bufio.NewWriter(tlsConn))
	} else {
		defer unsecuredConn.Close()
		myConn.ReadWriter = bufrw
	}
	handleConnection(myConn)
}

func getAuth(state tls.ConnectionState) (string, map[string]struct{}, error) {
	var username string
	permittedMethods := make(map[string]struct{})
	for _, certChain := range state.VerifiedChains {
		for _, cert := range certChain {
			var err error
			if username == "" {
				username, err = x509util.GetUsername(cert)
				if err != nil {
					return "", nil, err
				}
			}
			pms, err := x509util.GetPermittedMethods(cert)
			if err != nil {
				return "", nil, err
			}
			for method := range pms {
				permittedMethods[method] = struct{}{}
			}
		}
	}
	return username, permittedMethods, nil
}

func handleConnection(conn *Conn) {
	defer conn.Flush()
	for ; ; conn.Flush() {
		serviceMethod, err := conn.ReadString('\n')
		if err == io.EOF || err == io.ErrUnexpectedEOF {
			return
		}
		if err != nil {
			log.Println(err)
			if _, err := conn.WriteString(err.Error() + "\n"); err != nil {
				log.Println(err)
				return
			}
			continue
		}
		if serviceMethod == "\n" {
			// Received a "ping" request, send response.
			if _, err := conn.WriteString("\n"); err != nil {
				log.Println(err)
				return
			}
			continue
		}
		serviceMethod = serviceMethod[:len(serviceMethod)-1]
		method, err := findMethod(serviceMethod)
		if err != nil {
			if _, err := conn.WriteString(err.Error() + "\n"); err != nil {
				log.Println(err)
				return
			}
			continue
		}
		if !conn.checkPermitted(serviceMethod) {
			method.numDeniedCalls++
			if _, e := conn.WriteString(
				ErrorAccessToMethodDenied.Error() + "\n"); e != nil {
				log.Println(e)
				return
			}
			continue
		} else {
			if _, err := conn.WriteString("\n"); err != nil {
				log.Println(err)
				return
			}
		}
		if err := conn.Flush(); err != nil {
			log.Println(err)
			return
		}
		if err := method.call(conn); err != nil {
			log.Println(err)
			return
		}
	}
}

// Returns true if the method is permitted, else false if denied.
func (conn *Conn) checkPermitted(serviceMethod string) bool {
	if conn.permittedMethods == nil {
		return true
	}
	for sm := range conn.permittedMethods {
		if matched, _ := filepath.Match(sm, serviceMethod); matched {
			return true
		}
	}
	return false
}

func findMethod(serviceMethod string) (*methodWrapper, error) {
	splitServiceMethod := strings.Split(serviceMethod, ".")
	if len(splitServiceMethod) != 2 {
		return nil, errors.New("malformed Service.Method: " + serviceMethod)
	}
	serviceName := splitServiceMethod[0]
	receiver, ok := receivers[serviceName]
	if !ok {
		return nil, errors.New("unknown service: " + serviceName)
	}
	methodName := splitServiceMethod[1]
	method, ok := receiver.methods[methodName]
	if !ok {
		return nil, errors.New(serviceName + ": unknown method: " + methodName)
	}
	return method, nil
}

func listMethodsHttpHandler(w http.ResponseWriter, req *http.Request) {
	writer := bufio.NewWriter(w)
	defer writer.Flush()
	methods := make([]string, len(receivers))
	for receiverName, receiver := range receivers {
		for method := range receiver.methods {
			methods = append(methods, receiverName+"."+method+"\n")
		}
	}
	sort.Strings(methods)
	for _, method := range methods {
		writer.WriteString(method)
	}
}

func (m *methodWrapper) call(conn *Conn) error {
	m.numPermittedCalls++
	startTime := time.Now()
	err := m._call(conn)
	timeTaken := time.Since(startTime)
	if err == nil {
		m.successfulCallsDistribution.Add(timeTaken)
	} else {
		m.failedCallsDistribution.Add(timeTaken)
	}
	return err
}

func (m *methodWrapper) _call(conn *Conn) error {
	connValue := reflect.ValueOf(conn)
	if m.plain {
		returnValues := m.fn.Call([]reflect.Value{connValue})
		errInter := returnValues[0].Interface()
		if errInter != nil {
			return errInter.(error)
		}
		return nil
	}
	request := reflect.New(m.requestType)
	response := reflect.New(m.responseType)
	decoder := gob.NewDecoder(conn)
	if err := decoder.Decode(request.Interface()); err != nil {
		_, err = conn.WriteString(err.Error() + "\n")
		return err
	}
	startTime := time.Now()
	returnValues := m.fn.Call([]reflect.Value{connValue, request.Elem(),
		response})
	timeTaken := time.Since(startTime)
	errInter := returnValues[0].Interface()
	if errInter != nil {
		m.failedRRCallsDistribution.Add(timeTaken)
		err := errInter.(error)
		_, err = conn.WriteString(err.Error() + "\n")
		return err
	}
	m.successfulRRCallsDistribution.Add(timeTaken)
	if _, err := conn.WriteString("\n"); err != nil {
		return err
	}
	return gob.NewEncoder(conn).Encode(response.Interface())
}
<commit_msg>Add num-open-connections server metric in lib/srpc package.<commit_after>package srpc

import (
	"bufio"
	"crypto/tls"
	"encoding/gob"
	"errors"
	"github.com/Symantec/Dominator/lib/x509util"
	"github.com/Symantec/tricorder/go/tricorder"
	"github.com/Symantec/tricorder/go/tricorder/units"
	"io"
	"log"
	"net"
	"net/http"
	"path/filepath"
	"reflect"
	"sort"
	"strings"
	"sync"
	"time"
)

const (
	connectString   = "200 Connected to Go SRPC"
	rpcPath         = "/_goSRPC_/"
	tlsRpcPath      = "/_go_TLS_SRPC_/"
	listMethodsPath = rpcPath + "listMethods"
)

type methodWrapper struct {
	plain                         bool
	fn                            reflect.Value
	requestType                   reflect.Type
	responseType                  reflect.Type
	failedCallsDistribution       *tricorder.CumulativeDistribution
	failedRRCallsDistribution     *tricorder.CumulativeDistribution
	numDeniedCalls                uint64
	numPermittedCalls             uint64
	successfulCallsDistribution   *tricorder.CumulativeDistribution
	successfulRRCallsDistribution *tricorder.CumulativeDistribution
}

type receiverType struct {
	methods map[string]*methodWrapper
}

var (
	receivers              map[string]receiverType = make(map[string]receiverType)
	serverMetricsDir       *tricorder.DirectorySpec
	bucketer               *tricorder.Bucketer
	mutex                  sync.Mutex
	numConnections         uint64
	numOpenConnections     uint64
	numRejectedConnections uint64
)

// Precompute some reflect types. Can't use the types directly because Typeof
// takes an empty interface value. This is annoying.
var typeOfConn = reflect.TypeOf((**Conn)(nil)).Elem()
var typeOfError = reflect.TypeOf((*error)(nil)).Elem()

func init() {
	http.HandleFunc(rpcPath, unsecuredHttpHandler)
	http.HandleFunc(tlsRpcPath, tlsHttpHandler)
	http.HandleFunc(listMethodsPath, listMethodsHttpHandler)
	registerMetrics()
}

func registerMetrics() {
	var err error
	serverMetricsDir, err = tricorder.RegisterDirectory("srpc/server")
	if err != nil {
		panic(err)
	}
	err = serverMetricsDir.RegisterMetric("num-connections", &numConnections,
		units.None, "number of connection attempts")
	if err != nil {
		panic(err)
	}
	err = serverMetricsDir.RegisterMetric("num-open-connections",
		&numOpenConnections, units.None, "number of open connections")
	if err != nil {
		panic(err)
	}
	err = serverMetricsDir.RegisterMetric("num-rejected-connections",
		&numRejectedConnections, units.None, "number of rejected connections")
	if err != nil {
		panic(err)
	}
	bucketer = tricorder.NewGeometricBucketer(0.1, 1e5)
}

func registerName(name string, rcvr interface{}) error {
	var receiver receiverType
	receiver.methods = make(map[string]*methodWrapper)
	typeOfReceiver := reflect.TypeOf(rcvr)
	valueOfReceiver := reflect.ValueOf(rcvr)
	receiverMetricsDir, err := serverMetricsDir.RegisterDirectory(name)
	if err != nil {
		return err
	}
	for index := 0; index < typeOfReceiver.NumMethod(); index++ {
		method := typeOfReceiver.Method(index)
		if method.PkgPath != "" { // Method must be exported.
			continue
		}
		methodType := method.Type
		mVal := getMethod(methodType, valueOfReceiver.Method(index))
		if mVal == nil {
			continue
		}
		receiver.methods[method.Name] = mVal
		dir, err := receiverMetricsDir.RegisterDirectory(method.Name)
		if err != nil {
			return err
		}
		if err := mVal.registerMetrics(dir); err != nil {
			return err
		}
	}
	receivers[name] = receiver
	return nil
}

func getMethod(methodType reflect.Type, fn reflect.Value) *methodWrapper {
	if methodType.NumOut() != 1 {
		return nil
	}
	if methodType.Out(0) != typeOfError {
		return nil
	}
	if methodType.NumIn() == 2 {
		// Method needs two ins: receiver, *Conn.
		if methodType.In(1) != typeOfConn {
			return nil
		}
		return &methodWrapper{plain: true, fn: fn}
	}
	if methodType.NumIn() == 4 {
		// Method needs four ins: receiver, *Conn, request, *reply.
		if methodType.In(1) != typeOfConn {
			return nil
		}
		if methodType.In(3).Kind() != reflect.Ptr {
			return nil
		}
		return &methodWrapper{
			plain:        false,
			fn:           fn,
			requestType:  methodType.In(2),
			responseType: methodType.In(3).Elem()}
	}
	return nil
}

func (m *methodWrapper) registerMetrics(dir *tricorder.DirectorySpec) error {
	m.failedCallsDistribution = bucketer.NewCumulativeDistribution()
	err := dir.RegisterMetric("failed-call-durations",
		m.failedCallsDistribution, units.Millisecond,
		"duration of failed calls")
	if err != nil {
		return err
	}
	err = dir.RegisterMetric("num-denied-calls", &m.numDeniedCalls,
		units.None, "number of denied calls to method")
	if err != nil {
		return err
	}
	err = dir.RegisterMetric("num-permitted-calls", &m.numPermittedCalls,
		units.None, "number of permitted calls to method")
	if err != nil {
		return err
	}
	m.successfulCallsDistribution = bucketer.NewCumulativeDistribution()
	err = dir.RegisterMetric("successful-call-durations",
		m.successfulCallsDistribution, units.Millisecond,
		"duration of successful calls")
	if err != nil {
		return err
	}
	if m.plain {
		return nil
	}
	m.failedRRCallsDistribution = bucketer.NewCumulativeDistribution()
	err = dir.RegisterMetric("failed-request-reply-call-durations",
		m.failedRRCallsDistribution, units.Millisecond,
		"duration of failed request-reply calls")
	if err != nil {
		return err
	}
	m.successfulRRCallsDistribution = bucketer.NewCumulativeDistribution()
	err = dir.RegisterMetric("successful-request-reply-call-durations",
		m.successfulRRCallsDistribution, units.Millisecond,
		"duration of successful request-reply calls")
	if err != nil {
		return err
	}
	return nil
}

func unsecuredHttpHandler(w http.ResponseWriter, req *http.Request) {
	httpHandler(w, req, false)
}

func tlsHttpHandler(w http.ResponseWriter, req *http.Request) {
	httpHandler(w, req, true)
}

func httpHandler(w http.ResponseWriter, req *http.Request, doTls bool) {
	mutex.Lock()
	numConnections++
	mutex.Unlock()
	if doTls && serverTlsConfig == nil {
		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
		w.WriteHeader(http.StatusNotFound)
		return
	}
	if (tlsRequired && !doTls) || req.Method != "CONNECT" {
		mutex.Lock()
		numRejectedConnections++
		mutex.Unlock()
		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}
	unsecuredConn, bufrw, err := w.(http.Hijacker).Hijack()
	if err != nil {
		log.Println("rpc hijacking ", req.RemoteAddr, ": ", err.Error())
		return
	}
	if tcpConn, ok := unsecuredConn.(*net.TCPConn); ok {
		if err := tcpConn.SetKeepAlive(true); err != nil {
			log.Println("error setting keepalive: ", err.Error())
			return
		}
		if err := tcpConn.SetKeepAlivePeriod(time.Minute * 5); err != nil {
			log.Println("error setting keepalive period: ", err.Error())
			return
		}
	}
	_, err = io.WriteString(unsecuredConn, "HTTP/1.0 "+connectString+"\n\n")
	if err != nil {
		log.Println("error writing connect message: ", err.Error())
		return
	}
	myConn := new(Conn)
	if doTls {
		tlsConn := tls.Server(unsecuredConn, serverTlsConfig)
		defer tlsConn.Close()
		if err := tlsConn.Handshake(); err != nil {
			mutex.Lock()
			numRejectedConnections++
			mutex.Unlock()
			log.Println(err)
			return
		}
		myConn.isEncrypted = true
		myConn.username, myConn.permittedMethods, err = getAuth(
			tlsConn.ConnectionState())
		if err != nil {
			log.Println(err)
			return
		}
		myConn.ReadWriter = bufio.NewReadWriter(bufio.NewReader(tlsConn),
			bufio.NewWriter(tlsConn))
	} else {
		defer unsecuredConn.Close()
		myConn.ReadWriter = bufrw
	}
	mutex.Lock()
	numOpenConnections++
	mutex.Unlock()
	handleConnection(myConn)
	mutex.Lock()
	numOpenConnections--
	mutex.Unlock()
}

func getAuth(state tls.ConnectionState) (string, map[string]struct{}, error) {
	var username string
	permittedMethods := make(map[string]struct{})
	for _, certChain := range state.VerifiedChains {
		for _, cert := range certChain {
			var err error
			if username == "" {
				username, err = x509util.GetUsername(cert)
				if err != nil {
					return "", nil, err
				}
			}
			pms, err := x509util.GetPermittedMethods(cert)
			if err != nil {
				return "", nil, err
			}
			for method := range pms {
				permittedMethods[method] = struct{}{}
			}
		}
	}
	return username, permittedMethods, nil
}

func handleConnection(conn *Conn) {
	defer conn.Flush()
	for ; ; conn.Flush() {
		serviceMethod, err := conn.ReadString('\n')
		if err == io.EOF || err == io.ErrUnexpectedEOF {
			return
		}
		if err != nil {
			log.Println(err)
			if _, err := conn.WriteString(err.Error() + "\n"); err != nil {
				log.Println(err)
				return
			}
			continue
		}
		if serviceMethod == "\n" {
			// Received a "ping" request, send response.
			if _, err := conn.WriteString("\n"); err != nil {
				log.Println(err)
				return
			}
			continue
		}
		serviceMethod = serviceMethod[:len(serviceMethod)-1]
		method, err := findMethod(serviceMethod)
		if err != nil {
			if _, err := conn.WriteString(err.Error() + "\n"); err != nil {
				log.Println(err)
				return
			}
			continue
		}
		if !conn.checkPermitted(serviceMethod) {
			method.numDeniedCalls++
			if _, e := conn.WriteString(
				ErrorAccessToMethodDenied.Error() + "\n"); e != nil {
				log.Println(e)
				return
			}
			continue
		} else {
			if _, err := conn.WriteString("\n"); err != nil {
				log.Println(err)
				return
			}
		}
		if err := conn.Flush(); err != nil {
			log.Println(err)
			return
		}
		if err := method.call(conn); err != nil {
			log.Println(err)
			return
		}
	}
}

// Returns true if the method is permitted, else false if denied.
func (conn *Conn) checkPermitted(serviceMethod string) bool {
	if conn.permittedMethods == nil {
		return true
	}
	for sm := range conn.permittedMethods {
		if matched, _ := filepath.Match(sm, serviceMethod); matched {
			return true
		}
	}
	return false
}

func findMethod(serviceMethod string) (*methodWrapper, error) {
	splitServiceMethod := strings.Split(serviceMethod, ".")
	if len(splitServiceMethod) != 2 {
		return nil, errors.New("malformed Service.Method: " + serviceMethod)
	}
	serviceName := splitServiceMethod[0]
	receiver, ok := receivers[serviceName]
	if !ok {
		return nil, errors.New("unknown service: " + serviceName)
	}
	methodName := splitServiceMethod[1]
	method, ok := receiver.methods[methodName]
	if !ok {
		return nil, errors.New(serviceName + ": unknown method: " + methodName)
	}
	return method, nil
}

func listMethodsHttpHandler(w http.ResponseWriter, req *http.Request) {
	writer := bufio.NewWriter(w)
	defer writer.Flush()
	methods := make([]string, len(receivers))
	for receiverName, receiver := range receivers {
		for method := range receiver.methods {
			methods = append(methods, receiverName+"."+method+"\n")
		}
	}
	sort.Strings(methods)
	for _, method := range methods {
		writer.WriteString(method)
	}
}

func (m *methodWrapper) call(conn *Conn) error {
	m.numPermittedCalls++
	startTime := time.Now()
	err := m._call(conn)
	timeTaken := time.Since(startTime)
	if err == nil {
		m.successfulCallsDistribution.Add(timeTaken)
	} else {
		m.failedCallsDistribution.Add(timeTaken)
	}
	return err
}

func (m *methodWrapper) _call(conn *Conn) error {
	connValue := reflect.ValueOf(conn)
	if m.plain {
		returnValues := m.fn.Call([]reflect.Value{connValue})
		errInter := returnValues[0].Interface()
		if errInter != nil {
			return errInter.(error)
		}
		return nil
	}
	request := reflect.New(m.requestType)
	response := reflect.New(m.responseType)
	decoder := gob.NewDecoder(conn)
	if err := decoder.Decode(request.Interface()); err != nil {
		_, err = conn.WriteString(err.Error() + "\n")
		return err
	}
	startTime := time.Now()
	returnValues := m.fn.Call([]reflect.Value{connValue, request.Elem(),
		response})
	timeTaken := time.Since(startTime)
	errInter := returnValues[0].Interface()
	if errInter != nil {
		m.failedRRCallsDistribution.Add(timeTaken)
		err := errInter.(error)
		_, err = conn.WriteString(err.Error() + "\n")
		return err
	}
	m.successfulRRCallsDistribution.Add(timeTaken)
	if _, err := conn.WriteString("\n"); err != nil {
		return err
	}
	return gob.NewEncoder(conn).Encode(response.Interface())
}
<|endoftext|>


<commit_before>package endpoints_test

import (
	"context"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/lxc/lxd/lxd/endpoints"
	"github.com/lxc/lxd/lxd/util"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
)

// Return a new unstarted Endpoints instance, a Config with stub rest/devlxd
// servers, and a cleanup function that can be used to clear all state
// associated with the endpoints (e.g. the temporary LXD var dir and any
// goroutine that was spawned by the tomb).
func newEndpoints(t *testing.T) (*endpoints.Endpoints, *endpoints.Config, func()) {
	dir, err := ioutil.TempDir("", "lxd-endpoints-test-")
	require.NoError(t, err)
	require.NoError(t, os.Mkdir(filepath.Join(dir, "devlxd"), 0755))

	config := &endpoints.Config{
		Dir:          dir,
		UnixSocket:   filepath.Join(dir, "unix.socket"),
		RestServer:   newServer(),
		DevLxdServer: newServer(),
		Cert:         shared.TestingKeyPair(),
	}

	endpoints := endpoints.Unstarted()

	cleanup := func() {
		assert.NoError(t, endpoints.Down())

		// We need to kick the garbage collector because otherwise FDs
		// will be left open and confuse the http.GetListeners() code
		// that detects socket activation.
		runtime.GC()

		if shared.PathExists(dir) {
			require.NoError(t, os.RemoveAll(dir))
		}
	}

	return endpoints, config, cleanup
}

// Perform an HTTP GET "/" over the unix socket at the given path.
func httpGetOverUnixSocket(path string) error {
	dial := func(_ context.Context, network, addr string) (net.Conn, error) {
		return net.Dial("unix", path)
	}

	client := &http.Client{Transport: &http.Transport{DialContext: dial}}
	_, err := client.Get("http://unix.socket/")
	return err
}

// Perform an HTTP GET "/" over TLS, using the given network address and server
// certificate.
func httpGetOverTLSSocket(addr string, cert *shared.CertInfo) error {
	tlsConfig, _ := shared.GetTLSConfigMem("", "", "", string(cert.PublicKey()), false)
	client := &http.Client{Transport: &http.Transport{TLSClientConfig: tlsConfig}}
	_, err := client.Get(fmt.Sprintf("https://%s/", addr))
	return err
}

// Returns a minimal stub for the LXD RESTful API server, just realistic
// enough to make lxd.ConnectLXDUnix succeed.
func newServer() *http.Server {
	mux := http.NewServeMux()
	mux.HandleFunc("/1.0/", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		_ = util.WriteJSON(w, api.ResponseRaw{}, nil)
	})
	return &http.Server{Handler: mux, ErrorLog: log.New(ioutil.Discard, "", 0)}
}

// Set the environment-variable for socket-based activation using the given
// file.
func setupSocketBasedActivation(endpoints *endpoints.Endpoints, file *os.File) {
	_ = os.Setenv("LISTEN_PID", strconv.Itoa(os.Getpid()))
	_ = os.Setenv("LISTEN_FDS", "1")
	endpoints.SystemdListenFDsStart(int(file.Fd()))
}

// Assert that there are no socket-based activation variables in the
// environment.
func assertNoSocketBasedActivation(t *testing.T) {
	// The environment variables are automatically cleaned, to avoid
	// confusing child processes or other logic.
	for _, name := range []string{"LISTEN_PID", "LISTEN_FDS"} {
		_, ok := os.LookupEnv(name)
		assert.Equal(t, false, ok)
	}
}
<commit_msg>lxd/endpoints: Update endpoints test<commit_after>package endpoints_test

import (
	"context"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/lxc/lxd/lxd/endpoints"
	"github.com/lxc/lxd/lxd/util"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
)

// Return a new unstarted Endpoints instance, a Config with stub rest/devlxd
// servers, and a cleanup function that can be used to clear all state
// associated with the endpoints (e.g. the temporary LXD var dir and any
// goroutine that was spawned by the tomb).
func newEndpoints(t *testing.T) (*endpoints.Endpoints, *endpoints.Config, func()) {
	dir, err := ioutil.TempDir("", "lxd-endpoints-test-")
	require.NoError(t, err)
	require.NoError(t, os.Mkdir(filepath.Join(dir, "devlxd"), 0755))

	config := &endpoints.Config{
		Dir:          dir,
		UnixSocket:   filepath.Join(dir, "unix.socket"),
		RestServer:   newServer(),
		DevLxdServer: newServer(),
		Cert:         shared.TestingKeyPair(),
		VsockServer:  newServer(),
	}

	endpoints := endpoints.Unstarted()

	cleanup := func() {
		assert.NoError(t, endpoints.Down())

		// We need to kick the garbage collector because otherwise FDs
		// will be left open and confuse the http.GetListeners() code
		// that detects socket activation.
		runtime.GC()

		if shared.PathExists(dir) {
			require.NoError(t, os.RemoveAll(dir))
		}
	}

	return endpoints, config, cleanup
}

// Perform an HTTP GET "/" over the unix socket at the given path.
func httpGetOverUnixSocket(path string) error {
	dial := func(_ context.Context, network, addr string) (net.Conn, error) {
		return net.Dial("unix", path)
	}

	client := &http.Client{Transport: &http.Transport{DialContext: dial}}
	_, err := client.Get("http://unix.socket/")
	return err
}

// Perform an HTTP GET "/" over TLS, using the given network address and server
// certificate.
func httpGetOverTLSSocket(addr string, cert *shared.CertInfo) error {
	tlsConfig, _ := shared.GetTLSConfigMem("", "", "", string(cert.PublicKey()), false)
	client := &http.Client{Transport: &http.Transport{TLSClientConfig: tlsConfig}}
	_, err := client.Get(fmt.Sprintf("https://%s/", addr))
	return err
}

// Returns a minimal stub for the LXD RESTful API server, just realistic
// enough to make lxd.ConnectLXDUnix succeed.
func newServer() *http.Server {
	mux := http.NewServeMux()
	mux.HandleFunc("/1.0/", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		_ = util.WriteJSON(w, api.ResponseRaw{}, nil)
	})
	return &http.Server{Handler: mux, ErrorLog: log.New(ioutil.Discard, "", 0)}
}

// Set the environment-variable for socket-based activation using the given
// file.
func setupSocketBasedActivation(endpoints *endpoints.Endpoints, file *os.File) {
	_ = os.Setenv("LISTEN_PID", strconv.Itoa(os.Getpid()))
	_ = os.Setenv("LISTEN_FDS", "1")
	endpoints.SystemdListenFDsStart(int(file.Fd()))
}

// Assert that there are no socket-based activation variables in the
// environment.
func assertNoSocketBasedActivation(t *testing.T) {
	// The environment variables are automatically cleaned, to avoid
	// confusing child processes or other logic.
	for _, name := range []string{"LISTEN_PID", "LISTEN_FDS"} {
		_, ok := os.LookupEnv(name)
		assert.Equal(t, false, ok)
	}
}
<|endoftext|>


<commit_before>package timer_test

import (
	"fmt"
	"leaf/timer"
	"time"
)

func ExampleTimer() {
	d := timer.NewDispatcher(10)

	// timer 1
	d.AfterFunc(1, func() {
		fmt.Println("My name is Leaf")
	})

	// timer 2
	t := d.AfterFunc(1, func() {
		fmt.Println("will not print")
	})
	t.Stop()

	// dispatch
	(<-d.ChanTimer).Cb()

	// Output:
	// My name is Leaf
}

func ExampleCronExpr() {
	cronExpr, err := timer.NewCronExpr("0 * * * *")
	if err != nil {
		return
	}

	fmt.Println(cronExpr.Next(time.Date(
		2000, 1, 1,
		20, 10, 5,
		0, time.UTC,
	)))

	// Output:
	// 2000-01-01 21:00:00 +0000 UTC
}

func ExampleCron() {
	d := timer.NewDispatcher(10)

	// cron expr
	cronExpr, err := timer.NewCronExpr("* * * * * *")
	if err != nil {
		return
	}

	// cron
	var c *timer.Cron
	c = d.CronFunc(cronExpr, func() {
		fmt.Println("My name is Leaf")
		c.Stop()
	})

	// dispatch
	(<-d.ChanTimer).Cb()

	// Output:
	// My name is Leaf
}
<commit_msg>revert test<commit_after>package timer_test

import (
	"fmt"
	"github.com/abel/leaf/timer"
	"time"
)

func ExampleTimer() {
	d := timer.NewDispatcher(10)

	// timer 1
	d.AfterFunc(1, func() {
		fmt.Println("My name is Leaf")
	})

	// timer 2
	t := d.AfterFunc(1, func() {
		fmt.Println("will not print")
	})
	t.Stop()

	// dispatch
	(<-d.ChanTimer).Cb()

	// Output:
	// My name is Leaf
}

func ExampleCronExpr() {
	cronExpr, err := timer.NewCronExpr("0 * * * *")
	if err != nil {
		return
	}

	fmt.Println(cronExpr.Next(time.Date(
		2000, 1, 1,
		20, 10, 5,
		0, time.UTC,
	)))

	// Output:
	// 2000-01-01 21:00:00 +0000 UTC
}

func ExampleCron() {
	d := timer.NewDispatcher(10)

	// cron expr
	cronExpr, err := timer.NewCronExpr("* * * * * *")
	if err != nil {
		return
	}

	// cron
	var c *timer.Cron
	c = d.CronFunc(cronExpr, func() {
		fmt.Println("My name is Leaf")
		c.Stop()
	})

	// dispatch
	(<-d.ChanTimer).Cb()

	// Output:
	// My name is Leaf
}
<|endoftext|>