<commit_before>// +build linux freebsd solaris

package zfs

import (
	"fmt"
	"os"
	"os/exec"
	"path"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/docker/docker/daemon/graphdriver"
	"github.com/docker/docker/pkg/idtools"
	"github.com/docker/docker/pkg/mount"
	"github.com/docker/docker/pkg/parsers"
	zfs "github.com/mistifyio/go-zfs"
	"github.com/opencontainers/runc/libcontainer/label"
)

type zfsOptions struct {
	fsName    string
	mountPath string
}

func init() {
	graphdriver.Register("zfs", Init)
}

// Logger returns a zfs logger implementation.
type Logger struct{}

// Log wraps log message from ZFS driver with a prefix '[zfs]'.
func (*Logger) Log(cmd []string) {
	logrus.Debugf("[zfs] %s", strings.Join(cmd, " "))
}

// Init returns a new ZFS driver.
// It takes base mount path and an array of options which are represented as key value pairs.
// Each option is in the for key=value. 'zfs.fsname' is expected to be a valid key in the options.
func Init(base string, opt []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
	var err error

	if _, err := exec.LookPath("zfs"); err != nil {
		logrus.Debugf("[zfs] zfs command is not available: %v", err)
		return nil, graphdriver.ErrPrerequisites
	}

	file, err := os.OpenFile("/dev/zfs", os.O_RDWR, 600)
	if err != nil {
		logrus.Debugf("[zfs] cannot open /dev/zfs: %v", err)
		return nil, graphdriver.ErrPrerequisites
	}
	defer file.Close()

	options, err := parseOptions(opt)
	if err != nil {
		return nil, err
	}
	options.mountPath = base

	rootdir := path.Dir(base)

	if options.fsName == "" {
		err = checkRootdirFs(rootdir)
		if err != nil {
			return nil, err
		}
	}

	if options.fsName == "" {
		options.fsName, err = lookupZfsDataset(rootdir)
		if err != nil {
			return nil, err
		}
	}

	zfs.SetLogger(new(Logger))

	filesystems, err := zfs.Filesystems(options.fsName)
	if err != nil {
		return nil, fmt.Errorf("Cannot find root filesystem %s: %v", options.fsName, err)
	}

	filesystemsCache := make(map[string]bool, len(filesystems))
	var rootDataset *zfs.Dataset
	for _, fs := range filesystems {
		if fs.Name == options.fsName {
			rootDataset = fs
		}
		filesystemsCache[fs.Name] = true
	}

	if rootDataset == nil {
		return nil, fmt.Errorf("BUG: zfs get all -t filesystem -rHp '%s' should contain '%s'", options.fsName, options.fsName)
	}

	d := &Driver{
		dataset:          rootDataset,
		options:          options,
		filesystemsCache: filesystemsCache,
		uidMaps:          uidMaps,
		gidMaps:          gidMaps,
		ctr:              graphdriver.NewRefCounter(graphdriver.NewDefaultChecker()),
	}
	return graphdriver.NewNaiveDiffDriver(d, uidMaps, gidMaps), nil
}

func parseOptions(opt []string) (zfsOptions, error) {
	var options zfsOptions
	options.fsName = ""
	for _, option := range opt {
		key, val, err := parsers.ParseKeyValueOpt(option)
		if err != nil {
			return options, err
		}
		key = strings.ToLower(key)
		switch key {
		case "zfs.fsname":
			options.fsName = val
		default:
			return options, fmt.Errorf("Unknown option %s", key)
		}
	}
	return options, nil
}

func lookupZfsDataset(rootdir string) (string, error) {
	var stat syscall.Stat_t
	if err := syscall.Stat(rootdir, &stat); err != nil {
		return "", fmt.Errorf("Failed to access '%s': %s", rootdir, err)
	}
	wantedDev := stat.Dev

	mounts, err := mount.GetMounts()
	if err != nil {
		return "", err
	}
	for _, m := range mounts {
		if err := syscall.Stat(m.Mountpoint, &stat); err != nil {
			logrus.Debugf("[zfs] failed to stat '%s' while scanning for zfs mount: %v", m.Mountpoint, err)
			continue // may fail on fuse file systems
		}

		if stat.Dev == wantedDev && m.Fstype == "zfs" {
			return m.Source, nil
		}
	}

	return "", fmt.Errorf("Failed to find zfs dataset mounted on '%s' in /proc/mounts", rootdir)
}

// Driver holds information about the driver, such as zfs dataset, options and cache.
type Driver struct {
	dataset          *zfs.Dataset
	options          zfsOptions
	sync.Mutex       // protects filesystem cache against concurrent access
	filesystemsCache map[string]bool
	uidMaps          []idtools.IDMap
	gidMaps          []idtools.IDMap
	ctr              *graphdriver.RefCounter
}

func (d *Driver) String() string {
	return "zfs"
}

// Cleanup is used to implement graphdriver.ProtoDriver. There is no cleanup required for this driver.
func (d *Driver) Cleanup() error {
	return nil
}

// Status returns information about the ZFS filesystem. It returns a two dimensional array of information
// such as pool name, dataset name, disk usage, parent quota and compression used.
// Currently it return 'Zpool', 'Zpool Health', 'Parent Dataset', 'Space Used By Parent',
// 'Space Available', 'Parent Quota' and 'Compression'.
func (d *Driver) Status() [][2]string {
	parts := strings.Split(d.dataset.Name, "/")
	pool, err := zfs.GetZpool(parts[0])

	var poolName, poolHealth string
	if err == nil {
		poolName = pool.Name
		poolHealth = pool.Health
	} else {
		poolName = fmt.Sprintf("error while getting pool information %v", err)
		poolHealth = "not available"
	}

	quota := "no"
	if d.dataset.Quota != 0 {
		quota = strconv.FormatUint(d.dataset.Quota, 10)
	}

	return [][2]string{
		{"Zpool", poolName},
		{"Zpool Health", poolHealth},
		{"Parent Dataset", d.dataset.Name},
		{"Space Used By Parent", strconv.FormatUint(d.dataset.Used, 10)},
		{"Space Available", strconv.FormatUint(d.dataset.Avail, 10)},
		{"Parent Quota", quota},
		{"Compression", d.dataset.Compression},
	}
}

// GetMetadata returns image/container metadata related to graph driver
func (d *Driver) GetMetadata(id string) (map[string]string, error) {
	return nil, nil
}

func (d *Driver) cloneFilesystem(name, parentName string) error {
	snapshotName := fmt.Sprintf("%d", time.Now().Nanosecond())
	parentDataset := zfs.Dataset{Name: parentName}
	snapshot, err := parentDataset.Snapshot(snapshotName /*recursive */, false)
	if err != nil {
		return err
	}

	_, err = snapshot.Clone(name, map[string]string{"mountpoint": "legacy"})
	if err == nil {
		d.Lock()
		d.filesystemsCache[name] = true
		d.Unlock()
	}

	if err != nil {
		snapshot.Destroy(zfs.DestroyDeferDeletion)
		return err
	}
	return snapshot.Destroy(zfs.DestroyDeferDeletion)
}

func (d *Driver) zfsPath(id string) string {
	return d.options.fsName + "/" + id
}

func (d *Driver) mountPath(id string) string {
	return path.Join(d.options.mountPath, "graph", getMountpoint(id))
}

// CreateReadWrite creates a layer that is writable for use as a container
// file system.
func (d *Driver) CreateReadWrite(id, parent, mountLabel string, storageOpt map[string]string) error {
	return d.Create(id, parent, mountLabel, storageOpt)
}

// Create prepares the dataset and filesystem for the ZFS driver for the given id under the parent.
func (d *Driver) Create(id string, parent string, mountLabel string, storageOpt map[string]string) error {
	err := d.create(id, parent, storageOpt)
	if err == nil {
		return nil
	}
	if zfsError, ok := err.(*zfs.Error); ok {
		if !strings.HasSuffix(zfsError.Stderr, "dataset already exists\n") {
			return err
		}
		// aborted build -> cleanup
	} else {
		return err
	}

	dataset := zfs.Dataset{Name: d.zfsPath(id)}
	if err := dataset.Destroy(zfs.DestroyRecursiveClones); err != nil {
		return err
	}

	// retry
	return d.create(id, parent, storageOpt)
}

func (d *Driver) create(id, parent string, storageOpt map[string]string) error {
	name := d.zfsPath(id)
	quota, err := parseStorageOpt(storageOpt)
	if err != nil {
		return err
	}
	if parent == "" {
		mountoptions := map[string]string{"mountpoint": "legacy"}
		fs, err := zfs.CreateFilesystem(name, mountoptions)
		if err == nil {
			err = setQuota(name, quota)
			if err == nil {
				d.Lock()
				d.filesystemsCache[fs.Name] = true
				d.Unlock()
			}
		}
		return err
	}
	err = d.cloneFilesystem(name, d.zfsPath(parent))
	if err == nil {
		err = setQuota(name, quota)
	}
	return err
}

func parseStorageOpt(storageOpt map[string]string) (string, error) {
	// Read size to change the disk quota per container
	for k, v := range storageOpt {
		key := strings.ToLower(k)
		switch key {
		case "size":
			return v, nil
		default:
			return "0", fmt.Errorf("Unknown option %s", key)
		}
	}
	return "0", nil
}

func setQuota(name string, quota string) error {
	if quota == "0" {
		return nil
	}
	fs, err := zfs.GetDataset(name)
	if err != nil {
		return err
	}
	return fs.SetProperty("quota", quota)
}

// Remove deletes the dataset, filesystem and the cache for the given id.
func (d *Driver) Remove(id string) error {
	name := d.zfsPath(id)
	dataset := zfs.Dataset{Name: name}
	err := dataset.Destroy(zfs.DestroyRecursive)
	if err == nil {
		d.Lock()
		delete(d.filesystemsCache, name)
		d.Unlock()
	}
	return err
}

// Get returns the mountpoint for the given id after creating the target directories if necessary.
func (d *Driver) Get(id, mountLabel string) (string, error) {
	mountpoint := d.mountPath(id)
	if count := d.ctr.Increment(mountpoint); count > 1 {
		return mountpoint, nil
	}

	filesystem := d.zfsPath(id)
	options := label.FormatMountLabel("", mountLabel)
	logrus.Debugf(`[zfs] mount("%s", "%s", "%s")`, filesystem, mountpoint, options)

	rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
	if err != nil {
		d.ctr.Decrement(mountpoint)
		return "", err
	}
	// Create the target directories if they don't exist
	if err := idtools.MkdirAllAs(mountpoint, 0755, rootUID, rootGID); err != nil {
		d.ctr.Decrement(mountpoint)
		return "", err
	}

	if err := mount.Mount(filesystem, mountpoint, "zfs", options); err != nil {
		d.ctr.Decrement(mountpoint)
		return "", fmt.Errorf("error creating zfs mount of %s to %s: %v", filesystem, mountpoint, err)
	}

	// this could be our first mount after creation of the filesystem, and the root dir may still have root
	// permissions instead of the remapped root uid:gid (if user namespaces are enabled):
	if err := os.Chown(mountpoint, rootUID, rootGID); err != nil {
		mount.Unmount(mountpoint)
		d.ctr.Decrement(mountpoint)
		return "", fmt.Errorf("error modifying zfs mountpoint (%s) directory ownership: %v", mountpoint, err)
	}

	return mountpoint, nil
}

// Put removes the existing mountpoint for the given id if it exists.
func (d *Driver) Put(id string) error {
	mountpoint := d.mountPath(id)
	if count := d.ctr.Decrement(mountpoint); count > 0 {
		return nil
	}
	mounted, err := graphdriver.Mounted(graphdriver.FsMagicZfs, mountpoint)
	if err != nil || !mounted {
		return err
	}

	logrus.Debugf(`[zfs] unmount("%s")`, mountpoint)

	if err := mount.Unmount(mountpoint); err != nil {
		return fmt.Errorf("error unmounting to %s: %v", mountpoint, err)
	}
	return nil
}

// Exists checks to see if the cache entry exists for the given id.
func (d *Driver) Exists(id string) bool {
	d.Lock()
	defer d.Unlock()
	return d.filesystemsCache[d.zfsPath(id)] == true
}
<commit_msg>zfs: call mount.MakePrivate<commit_after>// +build linux freebsd solaris

package zfs

import (
	"fmt"
	"os"
	"os/exec"
	"path"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/docker/docker/daemon/graphdriver"
	"github.com/docker/docker/pkg/idtools"
	"github.com/docker/docker/pkg/mount"
	"github.com/docker/docker/pkg/parsers"
	zfs "github.com/mistifyio/go-zfs"
	"github.com/opencontainers/runc/libcontainer/label"
)

type zfsOptions struct {
	fsName    string
	mountPath string
}

func init() {
	graphdriver.Register("zfs", Init)
}

// Logger returns a zfs logger implementation.
type Logger struct{}

// Log wraps log message from ZFS driver with a prefix '[zfs]'.
func (*Logger) Log(cmd []string) {
	logrus.Debugf("[zfs] %s", strings.Join(cmd, " "))
}

// Init returns a new ZFS driver.
// It takes base mount path and an array of options which are represented as key value pairs.
// Each option is in the for key=value. 'zfs.fsname' is expected to be a valid key in the options.
func Init(base string, opt []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
	var err error

	if _, err := exec.LookPath("zfs"); err != nil {
		logrus.Debugf("[zfs] zfs command is not available: %v", err)
		return nil, graphdriver.ErrPrerequisites
	}

	file, err := os.OpenFile("/dev/zfs", os.O_RDWR, 600)
	if err != nil {
		logrus.Debugf("[zfs] cannot open /dev/zfs: %v", err)
		return nil, graphdriver.ErrPrerequisites
	}
	defer file.Close()

	options, err := parseOptions(opt)
	if err != nil {
		return nil, err
	}
	options.mountPath = base

	rootdir := path.Dir(base)

	if options.fsName == "" {
		err = checkRootdirFs(rootdir)
		if err != nil {
			return nil, err
		}
	}

	if options.fsName == "" {
		options.fsName, err = lookupZfsDataset(rootdir)
		if err != nil {
			return nil, err
		}
	}

	zfs.SetLogger(new(Logger))

	filesystems, err := zfs.Filesystems(options.fsName)
	if err != nil {
		return nil, fmt.Errorf("Cannot find root filesystem %s: %v", options.fsName, err)
	}

	filesystemsCache := make(map[string]bool, len(filesystems))
	var rootDataset *zfs.Dataset
	for _, fs := range filesystems {
		if fs.Name == options.fsName {
			rootDataset = fs
		}
		filesystemsCache[fs.Name] = true
	}

	if rootDataset == nil {
		return nil, fmt.Errorf("BUG: zfs get all -t filesystem -rHp '%s' should contain '%s'", options.fsName, options.fsName)
	}

	if err := mount.MakePrivate(base); err != nil {
		return nil, err
	}
	d := &Driver{
		dataset:          rootDataset,
		options:          options,
		filesystemsCache: filesystemsCache,
		uidMaps:          uidMaps,
		gidMaps:          gidMaps,
		ctr:              graphdriver.NewRefCounter(graphdriver.NewDefaultChecker()),
	}
	return graphdriver.NewNaiveDiffDriver(d, uidMaps, gidMaps), nil
}

func parseOptions(opt []string) (zfsOptions, error) {
	var options zfsOptions
	options.fsName = ""
	for _, option := range opt {
		key, val, err := parsers.ParseKeyValueOpt(option)
		if err != nil {
			return options, err
		}
		key = strings.ToLower(key)
		switch key {
		case "zfs.fsname":
			options.fsName = val
		default:
			return options, fmt.Errorf("Unknown option %s", key)
		}
	}
	return options, nil
}

func lookupZfsDataset(rootdir string) (string, error) {
	var stat syscall.Stat_t
	if err := syscall.Stat(rootdir, &stat); err != nil {
		return "", fmt.Errorf("Failed to access '%s': %s", rootdir, err)
	}
	wantedDev := stat.Dev

	mounts, err := mount.GetMounts()
	if err != nil {
		return "", err
	}
	for _, m := range mounts {
		if err := syscall.Stat(m.Mountpoint, &stat); err != nil {
			logrus.Debugf("[zfs] failed to stat '%s' while scanning for zfs mount: %v", m.Mountpoint, err)
			continue // may fail on fuse file systems
		}

		if stat.Dev == wantedDev && m.Fstype == "zfs" {
			return m.Source, nil
		}
	}

	return "", fmt.Errorf("Failed to find zfs dataset mounted on '%s' in /proc/mounts", rootdir)
}

// Driver holds information about the driver, such as zfs dataset, options and cache.
type Driver struct {
	dataset          *zfs.Dataset
	options          zfsOptions
	sync.Mutex       // protects filesystem cache against concurrent access
	filesystemsCache map[string]bool
	uidMaps          []idtools.IDMap
	gidMaps          []idtools.IDMap
	ctr              *graphdriver.RefCounter
}

func (d *Driver) String() string {
	return "zfs"
}

// Cleanup is used to implement graphdriver.ProtoDriver. There is no cleanup required for this driver.
func (d *Driver) Cleanup() error {
	return nil
}

// Status returns information about the ZFS filesystem. It returns a two dimensional array of information
// such as pool name, dataset name, disk usage, parent quota and compression used.
// Currently it return 'Zpool', 'Zpool Health', 'Parent Dataset', 'Space Used By Parent',
// 'Space Available', 'Parent Quota' and 'Compression'.
func (d *Driver) Status() [][2]string {
	parts := strings.Split(d.dataset.Name, "/")
	pool, err := zfs.GetZpool(parts[0])

	var poolName, poolHealth string
	if err == nil {
		poolName = pool.Name
		poolHealth = pool.Health
	} else {
		poolName = fmt.Sprintf("error while getting pool information %v", err)
		poolHealth = "not available"
	}

	quota := "no"
	if d.dataset.Quota != 0 {
		quota = strconv.FormatUint(d.dataset.Quota, 10)
	}

	return [][2]string{
		{"Zpool", poolName},
		{"Zpool Health", poolHealth},
		{"Parent Dataset", d.dataset.Name},
		{"Space Used By Parent", strconv.FormatUint(d.dataset.Used, 10)},
		{"Space Available", strconv.FormatUint(d.dataset.Avail, 10)},
		{"Parent Quota", quota},
		{"Compression", d.dataset.Compression},
	}
}

// GetMetadata returns image/container metadata related to graph driver
func (d *Driver) GetMetadata(id string) (map[string]string, error) {
	return nil, nil
}

func (d *Driver) cloneFilesystem(name, parentName string) error {
	snapshotName := fmt.Sprintf("%d", time.Now().Nanosecond())
	parentDataset := zfs.Dataset{Name: parentName}
	snapshot, err := parentDataset.Snapshot(snapshotName /*recursive */, false)
	if err != nil {
		return err
	}

	_, err = snapshot.Clone(name, map[string]string{"mountpoint": "legacy"})
	if err == nil {
		d.Lock()
		d.filesystemsCache[name] = true
		d.Unlock()
	}

	if err != nil {
		snapshot.Destroy(zfs.DestroyDeferDeletion)
		return err
	}
	return snapshot.Destroy(zfs.DestroyDeferDeletion)
}

func (d *Driver) zfsPath(id string) string {
	return d.options.fsName + "/" + id
}

func (d *Driver) mountPath(id string) string {
	return path.Join(d.options.mountPath, "graph", getMountpoint(id))
}

// CreateReadWrite creates a layer that is writable for use as a container
// file system.
func (d *Driver) CreateReadWrite(id, parent, mountLabel string, storageOpt map[string]string) error {
	return d.Create(id, parent, mountLabel, storageOpt)
}

// Create prepares the dataset and filesystem for the ZFS driver for the given id under the parent.
func (d *Driver) Create(id string, parent string, mountLabel string, storageOpt map[string]string) error {
	err := d.create(id, parent, storageOpt)
	if err == nil {
		return nil
	}
	if zfsError, ok := err.(*zfs.Error); ok {
		if !strings.HasSuffix(zfsError.Stderr, "dataset already exists\n") {
			return err
		}
		// aborted build -> cleanup
	} else {
		return err
	}

	dataset := zfs.Dataset{Name: d.zfsPath(id)}
	if err := dataset.Destroy(zfs.DestroyRecursiveClones); err != nil {
		return err
	}

	// retry
	return d.create(id, parent, storageOpt)
}

func (d *Driver) create(id, parent string, storageOpt map[string]string) error {
	name := d.zfsPath(id)
	quota, err := parseStorageOpt(storageOpt)
	if err != nil {
		return err
	}
	if parent == "" {
		mountoptions := map[string]string{"mountpoint": "legacy"}
		fs, err := zfs.CreateFilesystem(name, mountoptions)
		if err == nil {
			err = setQuota(name, quota)
			if err == nil {
				d.Lock()
				d.filesystemsCache[fs.Name] = true
				d.Unlock()
			}
		}
		return err
	}
	err = d.cloneFilesystem(name, d.zfsPath(parent))
	if err == nil {
		err = setQuota(name, quota)
	}
	return err
}

func parseStorageOpt(storageOpt map[string]string) (string, error) {
	// Read size to change the disk quota per container
	for k, v := range storageOpt {
		key := strings.ToLower(k)
		switch key {
		case "size":
			return v, nil
		default:
			return "0", fmt.Errorf("Unknown option %s", key)
		}
	}
	return "0", nil
}

func setQuota(name string, quota string) error {
	if quota == "0" {
		return nil
	}
	fs, err := zfs.GetDataset(name)
	if err != nil {
		return err
	}
	return fs.SetProperty("quota", quota)
}

// Remove deletes the dataset, filesystem and the cache for the given id.
func (d *Driver) Remove(id string) error {
	name := d.zfsPath(id)
	dataset := zfs.Dataset{Name: name}
	err := dataset.Destroy(zfs.DestroyRecursive)
	if err == nil {
		d.Lock()
		delete(d.filesystemsCache, name)
		d.Unlock()
	}
	return err
}

// Get returns the mountpoint for the given id after creating the target directories if necessary.
func (d *Driver) Get(id, mountLabel string) (string, error) {
	mountpoint := d.mountPath(id)
	if count := d.ctr.Increment(mountpoint); count > 1 {
		return mountpoint, nil
	}

	filesystem := d.zfsPath(id)
	options := label.FormatMountLabel("", mountLabel)
	logrus.Debugf(`[zfs] mount("%s", "%s", "%s")`, filesystem, mountpoint, options)

	rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
	if err != nil {
		d.ctr.Decrement(mountpoint)
		return "", err
	}
	// Create the target directories if they don't exist
	if err := idtools.MkdirAllAs(mountpoint, 0755, rootUID, rootGID); err != nil {
		d.ctr.Decrement(mountpoint)
		return "", err
	}

	if err := mount.Mount(filesystem, mountpoint, "zfs", options); err != nil {
		d.ctr.Decrement(mountpoint)
		return "", fmt.Errorf("error creating zfs mount of %s to %s: %v", filesystem, mountpoint, err)
	}

	// this could be our first mount after creation of the filesystem, and the root dir may still have root
	// permissions instead of the remapped root uid:gid (if user namespaces are enabled):
	if err := os.Chown(mountpoint, rootUID, rootGID); err != nil {
		mount.Unmount(mountpoint)
		d.ctr.Decrement(mountpoint)
		return "", fmt.Errorf("error modifying zfs mountpoint (%s) directory ownership: %v", mountpoint, err)
	}

	return mountpoint, nil
}

// Put removes the existing mountpoint for the given id if it exists.
func (d *Driver) Put(id string) error {
	mountpoint := d.mountPath(id)
	if count := d.ctr.Decrement(mountpoint); count > 0 {
		return nil
	}
	mounted, err := graphdriver.Mounted(graphdriver.FsMagicZfs, mountpoint)
	if err != nil || !mounted {
		return err
	}

	logrus.Debugf(`[zfs] unmount("%s")`, mountpoint)

	if err := mount.Unmount(mountpoint); err != nil {
		return fmt.Errorf("error unmounting to %s: %v", mountpoint, err)
	}
	return nil
}

// Exists checks to see if the cache entry exists for the given id.
func (d *Driver) Exists(id string) bool {
	d.Lock()
	defer d.Unlock()
	return d.filesystemsCache[d.zfsPath(id)] == true
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package fi

import (
	"fmt"

	"bytes"
	"github.com/golang/glog"
	"io"
	"k8s.io/kops/upup/pkg/fi/utils"
	"reflect"
	"strings"
)

// DryRunTarget is a special Target that does not execute anything, but instead tracks all changes.
// By running against a DryRunTarget, a list of changes that would be made can be easily collected,
// without any special support from the Tasks.
type DryRunTarget struct {
	changes   []*render
	deletions []Deletion

	// The destination to which the final report will be printed on Finish()
	out io.Writer
}

type render struct {
	a       Task
	aIsNil  bool
	e       Task
	changes Task
}

var _ Target = &DryRunTarget{}

func NewDryRunTarget(out io.Writer) *DryRunTarget {
	t := &DryRunTarget{}
	t.out = out
	return t
}

func (t *DryRunTarget) Render(a, e, changes Task) error {
	valA := reflect.ValueOf(a)
	aIsNil := valA.IsNil()

	t.changes = append(t.changes, &render{
		a:       a,
		aIsNil:  aIsNil,
		e:       e,
		changes: changes,
	})
	return nil
}

func (t *DryRunTarget) Delete(deletion Deletion) error {
	t.deletions = append(t.deletions, deletion)
	return nil
}

func IdForTask(taskMap map[string]Task, t Task) string {
	for k, v := range taskMap {
		if v == t {
			return k
		}
	}
	glog.Fatalf("unknown task: %v", t)
	return "?"
}

func (t *DryRunTarget) PrintReport(taskMap map[string]Task, out io.Writer) error {
	b := &bytes.Buffer{}

	if len(t.changes) != 0 {
		var creates []*render
		var updates []*render

		for _, r := range t.changes {
			if r.aIsNil {
				creates = append(creates, r)
			} else {
				updates = append(updates, r)
			}
		}

		if len(creates) != 0 {
			fmt.Fprintf(b, "Will create resources:\n")
			for _, r := range creates {
				taskName := getTaskName(r.changes)
				fmt.Fprintf(b, "  %-20s\t%s\n", taskName, IdForTask(taskMap, r.e))

				changes := reflect.ValueOf(r.changes)
				if changes.Kind() == reflect.Ptr && !changes.IsNil() {
					changes = changes.Elem()
				}

				if changes.Kind() == reflect.Struct {
					for i := 0; i < changes.NumField(); i++ {

						field := changes.Field(i)

						fieldName := changes.Type().Field(i).Name
						fieldValue := ValueAsString(field)

						// The field name is already printed above, no need to repeat it.
						// Additionally, ignore any output that is not informative
						if fieldName != "Name" && fieldValue != "<nil>" && fieldValue != "id:<nil>" && fieldValue != "<resource>" {
							fmt.Fprintf(b, "  \t%-20s\t%s\n", fieldName, fieldValue)
						}
					}
				}

				fmt.Fprintf(b, "\n")
			}
		}

		if len(updates) != 0 {
			fmt.Fprintf(b, "Will modify resources:\n")
			// We can't use our reflection helpers here - we want corresponding values from a,e,c
			for _, r := range updates {
				var changeList []string

				valC := reflect.ValueOf(r.changes)
				valA := reflect.ValueOf(r.a)
				valE := reflect.ValueOf(r.e)
				if valC.Kind() == reflect.Ptr && !valC.IsNil() {
					valC = valC.Elem()
				}
				if valA.Kind() == reflect.Ptr && !valA.IsNil() {
					valA = valA.Elem()
				}
				if valE.Kind() == reflect.Ptr && !valE.IsNil() {
					valE = valE.Elem()
				}
				if valC.Kind() == reflect.Struct {
					for i := 0; i < valC.NumField(); i++ {
						fieldValC := valC.Field(i)

						changed := true
						switch fieldValC.Kind() {
						case reflect.Ptr, reflect.Interface, reflect.Slice, reflect.Map:
							changed = !fieldValC.IsNil()

						case reflect.String:
							changed = fieldValC.Interface().(string) != ""
						}
						if !changed {
							continue
						}

						if fieldValC.Kind() == reflect.String && fieldValC.Interface().(string) == "" {
							// No change
							continue
						}

						fieldValE := valE.Field(i)

						description := ""
						ignored := false
						if fieldValE.CanInterface() {
							fieldValA := valA.Field(i)

							switch fieldValE.Interface().(type) {
							//case SimpleUnit:
							//	ignored = true
							default:
								description = fmt.Sprintf(" %v -> %v", ValueAsString(fieldValA), ValueAsString(fieldValE))
							}
						}
						if ignored {
							continue
						}
						changeList = append(changeList, fmt.Sprintf("%-20s\t%s", valC.Type().Field(i).Name, description))
					}
				} else {
					return fmt.Errorf("unhandled change type: %v", valC.Type())
				}

				if len(changeList) == 0 {
					continue
				}

				taskName := getTaskName(r.changes)
				fmt.Fprintf(b, "  %-20s\t%s\n", taskName, IdForTask(taskMap, r.e))
				for _, f := range changeList {
					fmt.Fprintf(b, "  \t%s\n", f)
				}
				fmt.Fprintf(b, "\n")
			}
		}
	}

	if len(t.deletions) != 0 {
		fmt.Fprintf(b, "Will delete items:\n")
		for _, d := range t.deletions {
			fmt.Fprintf(b, "  %-20s %s\n", d.TaskName(), d.Item())
		}
	}

	_, err := out.Write(b.Bytes())
	return err
}

func getTaskName(t Task) string {
	s := fmt.Sprintf("%T", t)
	lastDot := strings.LastIndexByte(s, '.')
	if lastDot != -1 {
		s = s[lastDot+1:]
	}
	return s
}

// asString returns a human-readable string representation of the passed value
func ValueAsString(value reflect.Value) string {
	b := &bytes.Buffer{}

	walker := func(path string, field *reflect.StructField, v reflect.Value) error {
		if utils.IsPrimitiveValue(v) || v.Kind() == reflect.String {
			fmt.Fprintf(b, "%v", v.Interface())
			return utils.SkipReflection
		}

		switch v.Kind() {
		case reflect.Ptr, reflect.Interface, reflect.Slice, reflect.Map:
			if v.IsNil() {
				fmt.Fprintf(b, "<nil>")
				return utils.SkipReflection
			}
		}

		switch v.Kind() {
		case reflect.Ptr, reflect.Interface:
			return nil // descend into value

		case reflect.Slice:
			len := v.Len()
			fmt.Fprintf(b, "[")
			for i := 0; i < len; i++ {
				av := v.Index(i)

				if i != 0 {
					fmt.Fprintf(b, ", ")
				}
				fmt.Fprintf(b, "%s", ValueAsString(av))
			}
			fmt.Fprintf(b, "]")
			return utils.SkipReflection

		case reflect.Map:
			keys := v.MapKeys()
			fmt.Fprintf(b, "{")
			for i, key := range keys {
				mv := v.MapIndex(key)

				if i != 0 {
					fmt.Fprintf(b, ", ")
				}
				fmt.Fprintf(b, "%s: %s", ValueAsString(key), ValueAsString(mv))
			}
			fmt.Fprintf(b, "}")
			return utils.SkipReflection

		case reflect.Struct:
			intf := v.Addr().Interface()
			if _, ok := intf.(Resource); ok {
				fmt.Fprintf(b, "<resource>")
			} else if _, ok := intf.(*ResourceHolder); ok {
				fmt.Fprintf(b, "<resource>")
			} else if compareWithID, ok := intf.(CompareWithID); ok {
				id := compareWithID.CompareWithID()
				if id == nil {
					fmt.Fprintf(b, "id:<nil>")
				} else {
					fmt.Fprintf(b, "id:%s", *id)
				}
			} else {
				glog.V(4).Infof("Unhandled kind in asString for %q: %T", path, v.Interface())
				fmt.Fprint(b, DebugAsJsonString(intf))
			}
			return utils.SkipReflection

		default:
			glog.Infof("Unhandled kind in asString for %q: %T", path, v.Interface())
			return fmt.Errorf("Unhandled kind for %q: %v", path, v.Kind())
		}
	}

	err := utils.ReflectRecursive(value, walker)
	if err != nil {
		glog.Fatalf("unexpected error during reflective walk: %v", err)
	}
	return b.String()
}

// Finish is called at the end of a run, and prints a list of changes to the configured Writer
func (t *DryRunTarget) Finish(taskMap map[string]Task) error {
	return t.PrintReport(taskMap, t.out)
}

// HasChanges returns true iff any changes would have been made
func (t *DryRunTarget) HasChanges() bool {
	return len(t.changes)+len(t.deletions) != 0
}
<commit_msg>Avoid concurrent append to slices in DryRunTarget<commit_after>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package fi

import (
	"bytes"
	"fmt"
	"io"
	"reflect"
	"strings"
	"sync"

	"github.com/golang/glog"
	"k8s.io/kops/upup/pkg/fi/utils"
)

// DryRunTarget is a special Target that does not execute anything, but instead tracks all changes.
// By running against a DryRunTarget, a list of changes that would be made can be easily collected,
// without any special support from the Tasks.
type DryRunTarget struct {
	mutex sync.Mutex

	changes   []*render
	deletions []Deletion

	// The destination to which the final report will be printed on Finish()
	out io.Writer
}

type render struct {
	a       Task
	aIsNil  bool
	e       Task
	changes Task
}

var _ Target = &DryRunTarget{}

func NewDryRunTarget(out io.Writer) *DryRunTarget {
	t := &DryRunTarget{}
	t.out = out
	return t
}

func (t *DryRunTarget) Render(a, e, changes Task) error {
	valA := reflect.ValueOf(a)
	aIsNil := valA.IsNil()

	t.mutex.Lock()
	defer t.mutex.Unlock()

	t.changes = append(t.changes, &render{
		a:       a,
		aIsNil:  aIsNil,
		e:       e,
		changes: changes,
	})
	return nil
}

func (t *DryRunTarget) Delete(deletion Deletion) error {
	t.mutex.Lock()
	defer t.mutex.Unlock()

	t.deletions = append(t.deletions, deletion)

	return nil
}

func IdForTask(taskMap map[string]Task, t Task) string {
	for k, v := range taskMap {
		if v == t {
			return k
		}
	}
	glog.Fatalf("unknown task: %v", t)
	return "?"
}

func (t *DryRunTarget) PrintReport(taskMap map[string]Task, out io.Writer) error {
	b := &bytes.Buffer{}

	if len(t.changes) != 0 {
		var creates []*render
		var updates []*render

		for _, r := range t.changes {
			if r.aIsNil {
				creates = append(creates, r)
			} else {
				updates = append(updates, r)
			}
		}

		if len(creates) != 0 {
			fmt.Fprintf(b, "Will create resources:\n")
			for _, r := range creates {
				taskName := getTaskName(r.changes)
				fmt.Fprintf(b, "  %-20s\t%s\n", taskName, IdForTask(taskMap, r.e))

				changes := reflect.ValueOf(r.changes)
				if changes.Kind() == reflect.Ptr && !changes.IsNil() {
					changes = changes.Elem()
				}

				if changes.Kind() == reflect.Struct {
					for i := 0; i < changes.NumField(); i++ {

						field := changes.Field(i)

						fieldName := changes.Type().Field(i).Name
						fieldValue := ValueAsString(field)

						// The field name is already printed above, no need to repeat it.
						// Additionally, ignore any output that is not informative
						if fieldName != "Name" && fieldValue != "<nil>" && fieldValue != "id:<nil>" && fieldValue != "<resource>" {
							fmt.Fprintf(b, "  \t%-20s\t%s\n", fieldName, fieldValue)
						}
					}
				}

				fmt.Fprintf(b, "\n")
			}
		}

		if len(updates) != 0 {
			fmt.Fprintf(b, "Will modify resources:\n")
			// We can't use our reflection helpers here - we want corresponding values from a,e,c
			for _, r := range updates {
				var changeList []string

				valC := reflect.ValueOf(r.changes)
				valA := reflect.ValueOf(r.a)
				valE := reflect.ValueOf(r.e)
				if valC.Kind() == reflect.Ptr && !valC.IsNil() {
					valC = valC.Elem()
				}
				if valA.Kind() == reflect.Ptr && !valA.IsNil() {
					valA = valA.Elem()
				}
				if valE.Kind() == reflect.Ptr && !valE.IsNil() {
					valE = valE.Elem()
				}
				if valC.Kind() == reflect.Struct {
					for i := 0; i < valC.NumField(); i++ {
						fieldValC := valC.Field(i)

						changed := true
						switch fieldValC.Kind() {
						case reflect.Ptr, reflect.Interface, reflect.Slice, reflect.Map:
							changed = !fieldValC.IsNil()

						case reflect.String:
							changed = fieldValC.Interface().(string) != ""
						}
						if !changed {
							continue
						}

						if fieldValC.Kind() == reflect.String && fieldValC.Interface().(string) == "" {
							// No change
							continue
						}

						fieldValE := valE.Field(i)

						description := ""
						ignored := false
						if fieldValE.CanInterface() {
							fieldValA := valA.Field(i)

							switch fieldValE.Interface().(type) {
							//case SimpleUnit:
							//	ignored = true
							default:
								description = fmt.Sprintf(" %v -> %v", ValueAsString(fieldValA), ValueAsString(fieldValE))
							}
						}
						if ignored {
							continue
						}
						changeList = append(changeList, fmt.Sprintf("%-20s\t%s", valC.Type().Field(i).Name, description))
					}
				} else {
					return fmt.Errorf("unhandled change type: %v", valC.Type())
				}

				if len(changeList) == 0 {
					continue
				}

				taskName := getTaskName(r.changes)
				fmt.Fprintf(b, "  %-20s\t%s\n", taskName, IdForTask(taskMap, r.e))
				for _, f := range changeList {
					fmt.Fprintf(b, "  \t%s\n", f)
				}
				fmt.Fprintf(b, "\n")
			}
		}
	}

	if len(t.deletions) != 0 {
		fmt.Fprintf(b, "Will delete items:\n")
		for _, d := range t.deletions {
			fmt.Fprintf(b, "  %-20s %s\n", d.TaskName(), d.Item())
		}
	}

	_, err := out.Write(b.Bytes())
	return err
}

func getTaskName(t Task) string {
	s := fmt.Sprintf("%T", t)
	lastDot := strings.LastIndexByte(s, '.')
	if lastDot != -1 {
		s = s[lastDot+1:]
	}
	return s
}

// asString returns a human-readable string representation of the passed value
func ValueAsString(value reflect.Value) string {
	b := &bytes.Buffer{}

	walker := func(path string, field *reflect.StructField, v reflect.Value) error {
		if utils.IsPrimitiveValue(v) || v.Kind() == reflect.String {
			fmt.Fprintf(b, "%v", v.Interface())
			return utils.SkipReflection
		}

		switch v.Kind() {
		case reflect.Ptr, reflect.Interface, reflect.Slice, reflect.Map:
			if v.IsNil() {
				fmt.Fprintf(b, "<nil>")
				return utils.SkipReflection
			}
		}

		switch v.Kind() {
		case reflect.Ptr, reflect.Interface:
			return nil // descend into value

		case reflect.Slice:
			len := v.Len()
			fmt.Fprintf(b, "[")
			for i := 0; i < len; i++ {
				av := v.Index(i)

				if i != 0 {
					fmt.Fprintf(b, ", ")
				}
				fmt.Fprintf(b, "%s", ValueAsString(av))
			}
			fmt.Fprintf(b, "]")
			return utils.SkipReflection

		case reflect.Map:
			keys := v.MapKeys()
			fmt.Fprintf(b, "{")
			for i, key := range keys {
				mv := v.MapIndex(key)

				if i != 0 {
					fmt.Fprintf(b, ", ")
				}
				fmt.Fprintf(b, "%s: %s", ValueAsString(key), ValueAsString(mv))
			}
			fmt.Fprintf(b, "}")
			return utils.SkipReflection

		case reflect.Struct:
			intf := v.Addr().Interface()
			if _, ok := intf.(Resource); ok {
				fmt.Fprintf(b, "<resource>")
			} else if _, ok := intf.(*ResourceHolder); ok {
				fmt.Fprintf(b, "<resource>")
			} else if compareWithID, ok := intf.(CompareWithID); ok {
				id := compareWithID.CompareWithID()
				if id == nil {
					fmt.Fprintf(b, "id:<nil>")
				} else {
					fmt.Fprintf(b, "id:%s", *id)
				}
			} else {
				glog.V(4).Infof("Unhandled kind in asString for %q: %T", path, v.Interface())
				fmt.Fprint(b, DebugAsJsonString(intf))
			}
			return utils.SkipReflection

		default:
			glog.Infof("Unhandled kind in asString for %q: %T", path, v.Interface())
			return fmt.Errorf("Unhandled kind for %q: %v", path, v.Kind())
		}
	}

	err := utils.ReflectRecursive(value, walker)
	if err != nil {
		glog.Fatalf("unexpected error during reflective walk: %v", err)
	}
	return b.String()
}

// Finish is called at the end of a run, and prints a list of changes to the configured Writer
func (t *DryRunTarget) Finish(taskMap map[string]Task) error {
	return t.PrintReport(taskMap, t.out)
}

// HasChanges returns true iff any changes would have been made
func (t *DryRunTarget) HasChanges() bool {
	return len(t.changes)+len(t.deletions) != 0
}
<|endoftext|>


<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, version 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/.

// Package gitd Implements Git Smart HTTP backend as a HTTP handler,
// using Git's C implementation as reference: https://github.com/git/git/blob/master/http-backend.c
package gitd

import (
	"bytes"
	"compress/gzip"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
)

func init() {
	if !checkGitVersion(2, 2, 1) {
		log.Fatalln("Git >= v2.2.1 is required")
	}
}

// http://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html
type option func(*handler)

// Internal handler
type handler struct {
	reposPath string
}

// ReposPath allows to set the root path where the Git bare repos live.
func ReposPath(rpath string) option {
	return func(l *handler) {
		l.reposPath = rpath
	}
}

// Handler configures the handler and returns an HTTP handler function.
func Handler(h http.Handler, opts ...option) http.Handler {
	reposPath, err := ioutil.TempDir(os.TempDir(), "gitd")
	if err != nil {
		log.Fatalf("%v\n", err)
	}

	// Default configuration.
	handler := &handler{
		reposPath: reposPath,
	}

	// Sets users specified configurations, overriding default ones.
	for _, opt := range opts {
		opt(handler)
	}

	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		handlers := map[*regexp.Regexp]func(http.ResponseWriter, *http.Request, string, string){
			regexp.MustCompile("(.*?)/git-upload-pack$"):  uploadPack,
			regexp.MustCompile("(.*?)/git-receive-pack$"): receivePack,
			regexp.MustCompile("(.*?)/info/refs$"):        infoRefs,
		}

		for re, fn := range handlers {
			if m := re.FindStringSubmatch(req.URL.Path); m != nil {
				repoPath := m[1]
				fn(w, req, handler.reposPath, repoPath)
				return
			}
		}
		h.ServeHTTP(w, req)
	})
}

// uploadPack runs git-upload-pack in a safe manner.
func uploadPack(w http.ResponseWriter, req *http.Request, reposPath, repoPath string) {
	if req.Method != "POST" {
		w.WriteHeader(http.StatusMethodNotAllowed)
		w.Write([]byte("Method Not Allowed"))
		return
	}
	process := "git-upload-pack"
	cwd := filepath.Join(reposPath, repoPath)

	headers := w.Header()
	headers.Add("Content-Type", fmt.Sprintf("application/x-%s-result", process))
	w.WriteHeader(http.StatusOK)

	cmd := exec.Command(process, "--stateless-rpc", ".")
	cmd.Dir = cwd

	body, err := decompress(req)
	if err != nil {
		log.Printf("[ERROR] Error attempting to decompress request body: %+v", err)
		body = req.Body
	}

	runCommand(w, body, cmd)
	req.Body.Close()
}

// receivePack runs git-receive-pack in a safe manner.
func receivePack(w http.ResponseWriter, req *http.Request, reposPath, repoPath string) {
	if req.Method != "POST" {
		w.WriteHeader(http.StatusMethodNotAllowed)
		w.Write([]byte("Method Not Allowed"))
		return
	}
	process := "git-receive-pack"
	cwd := filepath.Join(reposPath, repoPath)

	headers := w.Header()
	headers.Add("Content-Type", fmt.Sprintf("application/x-%s-result", process))
	w.WriteHeader(http.StatusOK)

	cmd := exec.Command(process, "--stateless-rpc", ".")
	cmd.Dir = cwd

	body, err := decompress(req)
	if err != nil {
		log.Printf("[ERROR] Error attempting to decompress request body: %+v", err)
		body = req.Body
	}

	runCommand(w, body, cmd)
	req.Body.Close()
}

// infoRefs returns Git object refs.
func infoRefs(w http.ResponseWriter, req *http.Request, reposPath, repoPath string) {
	if req.Method != "GET" {
		w.WriteHeader(http.StatusMethodNotAllowed)
		w.Write([]byte("Method Not Allowed"))
		return
	}

	process := req.URL.Query().Get("service")
	cwd := filepath.Join(reposPath, repoPath)

	if process != "git-receive-pack" && process != "git-upload-pack" {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Bad Request"))
		return
	}

	headers := w.Header()
	headers.Add("Content-Type", fmt.Sprintf("application/x-%s-advertisement", process))
	w.WriteHeader(http.StatusOK)

	w.Write(packetWrite(fmt.Sprintf("# service=%s\n", process)))
	w.Write(packetFlush())

	cmd := exec.Command(process, "--stateless-rpc", "--advertise-refs", ".")
	cmd.Dir = cwd

	body, err := decompress(req)
	if err != nil {
		log.Printf("[ERROR] Error attempting to decompress request body: %+v", err)
		body = req.Body
	}

	runCommand(w, body, cmd)
	req.Body.Close()
}

// runCommand executes a shell command and pipes its output to HTTP response writer.
// DO NOT expose this function directly to end users as it will create a security breach.
func runCommand(w io.Writer, r io.Reader, cmd *exec.Cmd) {
	if cmd.Dir != "" {
		cmd.Dir = sanitize(cmd.Dir)
	}

	log.Printf("[DEBUG] Running command from %s: %s %s ", cmd.Dir, cmd.Path, cmd.Args)

	stdin, err := cmd.StdinPipe()
	if err != nil {
		log.Printf("[ERROR] %v", err)
	}

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		log.Printf("[ERROR] %v", err)
	}

	if err := cmd.Start(); err != nil {
		log.Printf("[ERROR] %v", err)
	}

	io.Copy(stdin, r)
	io.Copy(w, stdout)
	cmd.Wait()
}

// packetWrite returns bytes of a git packet containing the given string
func packetWrite(str string) []byte {
	s := strconv.FormatInt(int64((len(str) + 4)), 16)

	m := len(s) % 4
	if m != 0 {
		s = strings.Repeat("0", 4-m) + s
	}

	return []byte(s + str)
}

func packetFlush() []byte {
	return []byte("0000")
}

// decompress unzips request body if it is compressed by Git clients.
func decompress(r *http.Request) (io.Reader, error) {
	encoding := r.Header.Get("Content-Encoding")
	if encoding != "gzip" && encoding != "x-gzip" {
		return r.Body, nil
	}

	return gzip.NewReader(r.Body)
}

// sanitize Sanitizes name to avoid overwriting sensitive system files
// or executing forbidden binaries
func sanitize(name string) string {
	// Gets rid of volume drive label in Windows
	if len(name) > 1 && name[1] == ':' && runtime.GOOS == "windows" {
		name = name[2:]
	}

	name = filepath.Clean(name)
	name = filepath.ToSlash(name)
	for strings.HasPrefix(name, "../") {
		name = name[3:]
	}
	return name
}

// checkGitVersion checks a given Git version and returns whether or not
// the required version is installed in the system.
func checkGitVersion(major, minor, patch int) bool {
	git, err := exec.LookPath("git")
	if err != nil {
		log.Printf("[ERROR] %v", err)
		return false
	}

	cmd := exec.Command(git, "--version")
	var stdout string
	if stdout, _, err = runAndLog(cmd); err != nil {
		log.Printf("[ERROR] %v", err)
		return false
	}

	output := strings.Split(stdout, "\n")
	if len(output) < 2 {
		log.Printf("[DEBUG] git version output: %v", output)
		return false
	}

	parts := strings.Split(output[0], " ")
	if len(parts) < 3 {
		log.Printf("[DEBUG] git version parts: %v", parts)
		return false
	}

	version := strings.Split(parts[2], ".")
	major2, _ := strconv.Atoi(version[0])
	minor2, _ := strconv.Atoi(version[1])
	patch2, _ := strconv.Atoi(version[2])

	if major2 < major && minor2 < minor && patch2 < patch {
		log.Printf("[INFO] git version not supported: %d.%d.%d < %d.%d.%d", major2, minor2, patch2, major, minor, patch)
		return false
	}

	return true
}

// Borrowed from https://github.com/mitchellh/packer/blob/master/builder/vmware/common/driver.go
// runAndLog executes Git commands and logs output.
func runAndLog(cmd *exec.Cmd) (string, string, error) {
	var stdout, stderr bytes.Buffer

	log.Printf("[GitD] Executing: %s %v", cmd.Path, cmd.Args[1:])
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err := cmd.Run()

	stdoutString := strings.TrimSpace(stdout.String())
	stderrString := strings.TrimSpace(stderr.String())

	if _, ok := err.(*exec.ExitError); ok {
		message := stderrString
		if message == "" {
			message = stdoutString
		}

		err = fmt.Errorf("[GitD] error: %s", message)
	}

	log.Printf("stdout: %s", stdoutString)
	log.Printf("stderr: %s", stderrString)

	// Replace these for Windows, we only want to deal with Unix
	// style line endings.
	returnStdout := strings.Replace(stdout.String(), "\r\n", "\n", -1)
	returnStderr := strings.Replace(stderr.String(), "\r\n", "\n", -1)

	return returnStdout, returnStderr, err
}
<commit_msg>Compile route matching regexps upon handler initialization<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, version 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/.

// Package gitd Implements Git Smart HTTP backend as a HTTP handler,
// using Git's C implementation as reference: https://github.com/git/git/blob/master/http-backend.c
package gitd

import (
	"bytes"
	"compress/gzip"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
)

func init() {
	if !checkGitVersion(2, 2, 1) {
		log.Fatalln("Git >= v2.2.1 is required")
	}
}

// http://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html
type option func(*handler)

// Internal handler
type handler struct {
	reposPath string
}

// ReposPath allows to set the root path where the Git bare repos live.
func ReposPath(rpath string) option {
	return func(l *handler) {
		l.reposPath = rpath
	}
}

// Handler configures the handler and returns an HTTP handler function.
func Handler(h http.Handler, opts ...option) http.Handler {
	reposPath, err := ioutil.TempDir(os.TempDir(), "gitd")
	if err != nil {
		log.Fatalf("%v\n", err)
	}

	// Default configuration.
	handler := &handler{
		reposPath: reposPath,
	}

	// Sets users specified configurations, overriding default ones.
	for _, opt := range opts {
		opt(handler)
	}

	handlers := map[*regexp.Regexp]func(http.ResponseWriter, *http.Request, string, string){
		regexp.MustCompile("(.*?)/git-upload-pack$"):  uploadPack,
		regexp.MustCompile("(.*?)/git-receive-pack$"): receivePack,
		regexp.MustCompile("(.*?)/info/refs$"):        infoRefs,
	}

	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		for re, fn := range handlers {
			if m := re.FindStringSubmatch(req.URL.Path); m != nil {
				repoPath := m[1]
				fn(w, req, handler.reposPath, repoPath)
				return
			}
		}
		h.ServeHTTP(w, req)
	})
}

// uploadPack runs git-upload-pack in a safe manner.
func uploadPack(w http.ResponseWriter, req *http.Request, reposPath, repoPath string) {
	if req.Method != "POST" {
		w.WriteHeader(http.StatusMethodNotAllowed)
		w.Write([]byte("Method Not Allowed"))
		return
	}
	process := "git-upload-pack"
	cwd := filepath.Join(reposPath, repoPath)

	headers := w.Header()
	headers.Add("Content-Type", fmt.Sprintf("application/x-%s-result", process))
	w.WriteHeader(http.StatusOK)

	cmd := exec.Command(process, "--stateless-rpc", ".")
	cmd.Dir = cwd

	body, err := decompress(req)
	if err != nil {
		log.Printf("[ERROR] Error attempting to decompress request body: %+v", err)
		body = req.Body
	}

	runCommand(w, body, cmd)
	req.Body.Close()
}

// receivePack runs git-receive-pack in a safe manner.
func receivePack(w http.ResponseWriter, req *http.Request, reposPath, repoPath string) {
	if req.Method != "POST" {
		w.WriteHeader(http.StatusMethodNotAllowed)
		w.Write([]byte("Method Not Allowed"))
		return
	}
	process := "git-receive-pack"
	cwd := filepath.Join(reposPath, repoPath)

	headers := w.Header()
	headers.Add("Content-Type", fmt.Sprintf("application/x-%s-result", process))
	w.WriteHeader(http.StatusOK)

	cmd := exec.Command(process, "--stateless-rpc", ".")
	cmd.Dir = cwd

	body, err := decompress(req)
	if err != nil {
		log.Printf("[ERROR] Error attempting to decompress request body: %+v", err)
		body = req.Body
	}

	runCommand(w, body, cmd)
	req.Body.Close()
}

// infoRefs returns Git object refs.
func infoRefs(w http.ResponseWriter, req *http.Request, reposPath, repoPath string) {
	if req.Method != "GET" {
		w.WriteHeader(http.StatusMethodNotAllowed)
		w.Write([]byte("Method Not Allowed"))
		return
	}

	process := req.URL.Query().Get("service")
	cwd := filepath.Join(reposPath, repoPath)

	if process != "git-receive-pack" && process != "git-upload-pack" {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Bad Request"))
		return
	}

	headers := w.Header()
	headers.Add("Content-Type", fmt.Sprintf("application/x-%s-advertisement", process))
	w.WriteHeader(http.StatusOK)

	w.Write(packetWrite(fmt.Sprintf("# service=%s\n", process)))
	w.Write(packetFlush())

	cmd := exec.Command(process, "--stateless-rpc", "--advertise-refs", ".")
	cmd.Dir = cwd

	body, err := decompress(req)
	if err != nil {
		log.Printf("[ERROR] Error attempting to decompress request body: %+v", err)
		body = req.Body
	}

	runCommand(w, body, cmd)
	req.Body.Close()
}

// runCommand executes a shell command and pipes its output to HTTP response writer.
// DO NOT expose this function directly to end users as it will create a security breach.
func runCommand(w io.Writer, r io.Reader, cmd *exec.Cmd) {
	if cmd.Dir != "" {
		cmd.Dir = sanitize(cmd.Dir)
	}

	log.Printf("[DEBUG] Running command from %s: %s %s ", cmd.Dir, cmd.Path, cmd.Args)

	stdin, err := cmd.StdinPipe()
	if err != nil {
		log.Printf("[ERROR] %v", err)
	}

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		log.Printf("[ERROR] %v", err)
	}

	if err := cmd.Start(); err != nil {
		log.Printf("[ERROR] %v", err)
	}

	io.Copy(stdin, r)
	io.Copy(w, stdout)
	cmd.Wait()
}

// packetWrite returns bytes of a git packet containing the given string
func packetWrite(str string) []byte {
	s := strconv.FormatInt(int64((len(str) + 4)), 16)

	m := len(s) % 4
	if m != 0 {
		s = strings.Repeat("0", 4-m) + s
	}

	return []byte(s + str)
}

func packetFlush() []byte {
	return []byte("0000")
}

// decompress unzips request body if it is compressed by Git clients.
func decompress(r *http.Request) (io.Reader, error) {
	encoding := r.Header.Get("Content-Encoding")
	if encoding != "gzip" && encoding != "x-gzip" {
		return r.Body, nil
	}

	return gzip.NewReader(r.Body)
}

// sanitize Sanitizes name to avoid overwriting sensitive system files
// or executing forbidden binaries
func sanitize(name string) string {
	// Gets rid of volume drive label in Windows
	if len(name) > 1 && name[1] == ':' && runtime.GOOS == "windows" {
		name = name[2:]
	}

	name = filepath.Clean(name)
	name = filepath.ToSlash(name)
	for strings.HasPrefix(name, "../") {
		name = name[3:]
	}
	return name
}

// checkGitVersion checks a given Git version and returns whether or not
// the required version is installed in the system.
func checkGitVersion(major, minor, patch int) bool {
	git, err := exec.LookPath("git")
	if err != nil {
		log.Printf("[ERROR] %v", err)
		return false
	}

	cmd := exec.Command(git, "--version")
	var stdout string
	if stdout, _, err = runAndLog(cmd); err != nil {
		log.Printf("[ERROR] %v", err)
		return false
	}

	output := strings.Split(stdout, "\n")
	if len(output) < 2 {
		log.Printf("[DEBUG] git version output: %v", output)
		return false
	}

	parts := strings.Split(output[0], " ")
	if len(parts) < 3 {
		log.Printf("[DEBUG] git version parts: %v", parts)
		return false
	}

	version := strings.Split(parts[2], ".")
	major2, _ := strconv.Atoi(version[0])
	minor2, _ := strconv.Atoi(version[1])
	patch2, _ := strconv.Atoi(version[2])

	if major2 < major && minor2 < minor && patch2 < patch {
		log.Printf("[INFO] git version not supported: %d.%d.%d < %d.%d.%d", major2, minor2, patch2, major, minor, patch)
		return false
	}

	return true
}

// Borrowed from https://github.com/mitchellh/packer/blob/master/builder/vmware/common/driver.go
// runAndLog executes Git commands and logs output.
func runAndLog(cmd *exec.Cmd) (string, string, error) {
	var stdout, stderr bytes.Buffer

	log.Printf("[GitD] Executing: %s %v", cmd.Path, cmd.Args[1:])
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err := cmd.Run()

	stdoutString := strings.TrimSpace(stdout.String())
	stderrString := strings.TrimSpace(stderr.String())

	if _, ok := err.(*exec.ExitError); ok {
		message := stderrString
		if message == "" {
			message = stdoutString
		}

		err = fmt.Errorf("[GitD] error: %s", message)
	}

	log.Printf("stdout: %s", stdoutString)
	log.Printf("stderr: %s", stderrString)

	// Replace these for Windows, we only want to deal with Unix
	// style line endings.
	returnStdout := strings.Replace(stdout.String(), "\r\n", "\n", -1)
	returnStderr := strings.Replace(stderr.String(), "\r\n", "\n", -1)

	return returnStdout, returnStderr, err
}
<|endoftext|>


<commit_before>// Copyright (C) 2012, Johan P. P. Samyn <johan.samyn@gmail.com> All rights reserved.
// Use of this source code is governed by the Simplified BSD License
// that can be found in the LICENSE.txt file.

// Package gohg is a Go client library for using the Mercurial dvcs
// via it's Command Server.
//
// For Mercurial see: http://mercurial/selenic.com/wiki
// For the Hg Command Server see: http://mercurial.selenic.com/wiki/CommandServer
package gohg

import (
	"encoding/binary"
	"bytes"
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"path"
	"path/filepath"
)

var repo string
var err error
var libdir string
var logfile string

var hgserver *exec.Cmd

// The in and out pipe ends are to be considered from the point of view
// of the Hg Command Server instance. So closing pin closes the Hg CS.
var pout io.ReadCloser
var pin io.WriteCloser

type hgMsg struct {
	Ch   string
	Ln   uint
	Data string
}

type hgCmd struct {
	Cmd  string
	Ln   uint
	Args string
}

// init takes care of some householding, namely preparing a logfile where
// all communication between this lib and the Hg CS can be logged.
func init() {
	var exedir string
	exedir = path.Dir(os.Args[0])
	exedir, err = filepath.Abs(exedir)
	if err != nil {
		log.Fatal("Could not determine path for the gohg.log logfile.")
	}
	logfile = exedir + string(os.PathSeparator) + "gohg.log"
} // init()

// Connect establishes the connection with the Mercurial CommandServer.
//
// If anything went wrong when trying to make the connection,
// an error is returned.
//
// Parameters:
//		hg			The command to run mercurial. Optional.
//		reponame	The folder of the Hg repository to work on. Optional.
//					When blanc the folder where the program is run is used
//					(see locateRepository).
//		config		Configuration settings that will be added to the necessary
//					default/fixed settings (see composeHgConfig() for more).
// Returns an error if the connection could not be established flawlessly.
func Connect(hg string, reponame string, config []string) error {

	// for example:
	// hgserver = exec.Command("M:\\DEV\\hg-stable\\hg",	// the Hg command
	// 		"-R", "C:\\DEV\\go\\src\\golout\\",				// the repo
	// 		"--config", "ui.interactive=True",				// mandatory settings
	// 		"--config", "extensions.color=!",				// more settings (for Windows)
	// 		"serve", "--cmdserver", "pipe")					// start the Command Server

	// Maybe accept a channel as an extra argument for sending the logging to ?
	// And if it's nil, log into a textfile in the folder of this lib.
	// Also do not override that logfile every launch.
	// Maybe even do this in the init() function ?

	if hgserver != nil {
		return errors.New("A Hg Command Server is already connected.")
	}

	if hg == "" {
		// Let the OS determine what Mercurial to run
		// for this machine/user combination.
		hg = "hg"
	}
	// fmt.Printf("hg: %s\n", hg)

	// The Hg Command Server needs a repository.
	repo, err = locateRepository(reponame)
	if err != nil {
		return err
	}
	if repo == "" {
		return errors.New("could not find a Hg repository at: " + reponame)
	}
	// fmt.Printf("repo: %s\n", repo)

	// Maybe we can also offer the possibility of a config file?
	// f.i.: a file gohg.cfg in the same folder as the gohg.exe,
	// and a section per repo, and one "general" section.
	// Or maybe just a [gohg] section in one of the 'normal' Hg config files ?

	var hgconfig []string
	hgconfig = composeHgConfig(hg, repo, config)
	// fmt.Printf("hgconfig: %v\n", hgconfig)

	hgserver = exec.Command(hg)
	hgserver.Args = hgconfig
	hgserver.Dir = repo
	// fmt.Printf("hgserver: %v\n", hgserver)

	pout, err = hgserver.StdoutPipe()
	if err != nil {
		return errors.New("could not connect StdoutPipe: " + err.Error())
	}
	pin, err = hgserver.StdinPipe()
	if err != nil {
		log.Fatal("could not connect StdinPipe: " + err.Error())
	}

	if err := hgserver.Start(); err != nil {
		return errors.New("could not start the Hg Command Server: " + err.Error())
	}

	err = readHelloMessage()
	if err != nil {
		return err
	}

	fmt.Println("Connected with Hg Command Server at: " + repo)

	return nil

} // Connect()

// locateRepository takes care of assuring we have a Merurial repository
// available for working on via the Hg CommandServer, as that is necessary
// to be able to use the Hg CS.
func locateRepository(reponame string) (string, error) {
	repo = reponame
	sep := string(os.PathSeparator)

	// first make a correct path from repo
	repo, err = filepath.Abs(repo)
	if err != nil {
		return "", errors.New(err.Error() +
			"\ncould not find absolute path for: " + repo)
	}
	repo = filepath.Clean(repo)

	// If we do not find a Hg repo in this dir, we search for one
	// up the path, in case we're deeper in it's working copy.
	for {
		_, err = os.Stat(repo + sep + ".hg")
		if err == nil {
			break
		}
		var dir, file string
		dir, file = filepath.Split(repo)
		if dir == "" || file == "" {
			repo = ""
			break
		}
		repo = dir
	}
	if err != nil || repo == "" {
		return "", nil
	}

	return repo, nil

} // locateRepository()

// composeHgConfig handles the different config settings that will be used
// to make the connection with the Hg CS. It concerns settings specially for Hg.
func composeHgConfig(hgcmd string, repo string, config []string) []string {
	var hgconfig []string

	// if len(config) > 0 {
	// 	var cfg string
	// 	for i := 0; i < range(config) {
	// 		cfg = cfg + "," + config[i]
	// 	}
	// 	cmd = cmd + "," + cfg
	// }

	hgconfig = append(hgconfig, hgcmd,
		"-R", repo,
		// These arguments are fixed.
		"--config", "ui.interactive=True",
		"--config", "extensions.color=!",
		"serve", "--cmdserver", "pipe")

	return hgconfig
}

// readHelloMessage reads the special hello message send by the Hg CS.
//
// It has a fixed format, and contains info about the possibilities
// of the Hg CS at hand. It's also a first proof of a working connection.
func readHelloMessage() error {
	const t1 = "capabilities:"
	const t2 = "hg serve [OPTION]"

	s := make([]byte, 1+4+1024)
	_, err = pout.Read(s)
	if err != io.EOF && err != nil {
		return err
	}
	// fmt.Printf("s: %s\n", s)
	if len(s) == 0 {
		return errors.New("no data received from Hg Command Server")
	}
	var ln uint32
	buf := bytes.NewBuffer(s[1:5])
	err = binary.Read(buf, binary.BigEndian, &ln)
	if err != nil {
		fmt.Println("binary.Read failed:", err)
	}
	l := len(t2)
	if string(s[0:l]) == t2 {
		return errors.New("this version of Mercurial does not support the Command Server")
	}
	l = len(t1)
	if string(s[5:5+l]) != t1 {
		return errors.New("could not connect a Hg Command Server")
	}
	return nil
}

// Close ends the conection with the Mercurial CommandServer.
//
// In fact it's closing the stdin of the Hg CS that closes connection,
// as per the Hg CS documentation.
func Close() error {
	pout.Close()
	// Closing it's stdin is what really closes the Hg Command Server.
	pin.Close()
	err = hgserver.Wait()
	if err != nil {
		return err
	}
	fmt.Println("Disconnected from Hg Command Server at: " + repo)
	return nil
} // Close()
<commit_msg>rearranged the main comment<commit_after>// Copyright (C) 2012, Johan P. P. Samyn <johan.samyn@gmail.com> All rights reserved.
// Use of this source code is governed by the Simplified BSD License
// that can be found in the LICENSE.txt file.

// Package gohg is a Go client library for using the Mercurial dvcs
// via it's Command Server.
//
// For Mercurial see: http://mercurial/selenic.com/wiki.
//
// For the Hg Command Server see: http://mercurial.selenic.com/wiki/CommandServer.
package gohg

import (
	"encoding/binary"
	"bytes"
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"path"
	"path/filepath"
)

var repo string
var err error
var libdir string
var logfile string

var hgserver *exec.Cmd

// The in and out pipe ends are to be considered from the point of view
// of the Hg Command Server instance. So closing pin closes the Hg CS.
var pout io.ReadCloser
var pin io.WriteCloser

type hgMsg struct {
	Ch   string
	Ln   uint
	Data string
}

type hgCmd struct {
	Cmd  string
	Ln   uint
	Args string
}

// init takes care of some householding, namely preparing a logfile where
// all communication between this lib and the Hg CS can be logged.
func init() {
	var exedir string
	exedir = path.Dir(os.Args[0])
	exedir, err = filepath.Abs(exedir)
	if err != nil {
		log.Fatal("Could not determine path for the gohg.log logfile.")
	}
	logfile = exedir + string(os.PathSeparator) + "gohg.log"
} // init()

// Connect establishes the connection with the Mercurial CommandServer.
//
// If anything went wrong when trying to make the connection,
// an error is returned.
//
// Parameters:
//		hg			The command to run mercurial. Optional.
//		reponame	The folder of the Hg repository to work on. Optional.
//					When blanc the folder where the program is run is used
//					(see locateRepository).
//		config		Configuration settings that will be added to the necessary
//					default/fixed settings (see composeHgConfig() for more).
// Returns an error if the connection could not be established flawlessly.
func Connect(hg string, reponame string, config []string) error {

	// for example:
	// hgserver = exec.Command("M:\\DEV\\hg-stable\\hg",	// the Hg command
	// 		"-R", "C:\\DEV\\go\\src\\golout\\",				// the repo
	// 		"--config", "ui.interactive=True",				// mandatory settings
	// 		"--config", "extensions.color=!",				// more settings (for Windows)
	// 		"serve", "--cmdserver", "pipe")					// start the Command Server

	// Maybe accept a channel as an extra argument for sending the logging to ?
	// And if it's nil, log into a textfile in the folder of this lib.
	// Also do not override that logfile every launch.
	// Maybe even do this in the init() function ?

	if hgserver != nil {
		return errors.New("A Hg Command Server is already connected.")
	}

	if hg == "" {
		// Let the OS determine what Mercurial to run
		// for this machine/user combination.
		hg = "hg"
	}
	// fmt.Printf("hg: %s\n", hg)

	// The Hg Command Server needs a repository.
	repo, err = locateRepository(reponame)
	if err != nil {
		return err
	}
	if repo == "" {
		return errors.New("could not find a Hg repository at: " + reponame)
	}
	// fmt.Printf("repo: %s\n", repo)

	// Maybe we can also offer the possibility of a config file?
	// f.i.: a file gohg.cfg in the same folder as the gohg.exe,
	// and a section per repo, and one "general" section.
	// Or maybe just a [gohg] section in one of the 'normal' Hg config files ?

	var hgconfig []string
	hgconfig = composeHgConfig(hg, repo, config)
	// fmt.Printf("hgconfig: %v\n", hgconfig)

	hgserver = exec.Command(hg)
	hgserver.Args = hgconfig
	hgserver.Dir = repo
	// fmt.Printf("hgserver: %v\n", hgserver)

	pout, err = hgserver.StdoutPipe()
	if err != nil {
		return errors.New("could not connect StdoutPipe: " + err.Error())
	}
	pin, err = hgserver.StdinPipe()
	if err != nil {
		log.Fatal("could not connect StdinPipe: " + err.Error())
	}

	if err := hgserver.Start(); err != nil {
		return errors.New("could not start the Hg Command Server: " + err.Error())
	}

	err = readHelloMessage()
	if err != nil {
		return err
	}

	fmt.Println("Connected with Hg Command Server at: " + repo)

	return nil

} // Connect()

// locateRepository takes care of assuring we have a Merurial repository
// available for working on via the Hg CommandServer, as that is necessary
// to be able to use the Hg CS.
func locateRepository(reponame string) (string, error) {
	repo = reponame
	sep := string(os.PathSeparator)

	// first make a correct path from repo
	repo, err = filepath.Abs(repo)
	if err != nil {
		return "", errors.New(err.Error() +
			"\ncould not find absolute path for: " + repo)
	}
	repo = filepath.Clean(repo)

	// If we do not find a Hg repo in this dir, we search for one
	// up the path, in case we're deeper in it's working copy.
	for {
		_, err = os.Stat(repo + sep + ".hg")
		if err == nil {
			break
		}
		var dir, file string
		dir, file = filepath.Split(repo)
		if dir == "" || file == "" {
			repo = ""
			break
		}
		repo = dir
	}
	if err != nil || repo == "" {
		return "", nil
	}

	return repo, nil

} // locateRepository()

// composeHgConfig handles the different config settings that will be used
// to make the connection with the Hg CS. It concerns settings specially for Hg.
func composeHgConfig(hgcmd string, repo string, config []string) []string {
	var hgconfig []string

	// if len(config) > 0 {
	// 	var cfg string
	// 	for i := 0; i < range(config) {
	// 		cfg = cfg + "," + config[i]
	// 	}
	// 	cmd = cmd + "," + cfg
	// }

	hgconfig = append(hgconfig, hgcmd,
		"-R", repo,
		// These arguments are fixed.
		"--config", "ui.interactive=True",
		"--config", "extensions.color=!",
		"serve", "--cmdserver", "pipe")

	return hgconfig
}

// readHelloMessage reads the special hello message send by the Hg CS.
//
// It has a fixed format, and contains info about the possibilities
// of the Hg CS at hand. It's also a first proof of a working connection.
func readHelloMessage() error {
	const t1 = "capabilities:"
	const t2 = "hg serve [OPTION]"

	s := make([]byte, 1+4+1024)
	_, err = pout.Read(s)
	if err != io.EOF && err != nil {
		return err
	}
	// fmt.Printf("s: %s\n", s)
	if len(s) == 0 {
		return errors.New("no data received from Hg Command Server")
	}
	var ln uint32
	buf := bytes.NewBuffer(s[1:5])
	err = binary.Read(buf, binary.BigEndian, &ln)
	if err != nil {
		fmt.Println("binary.Read failed:", err)
	}
	l := len(t2)
	if string(s[0:l]) == t2 {
		return errors.New("this version of Mercurial does not support the Command Server")
	}
	l = len(t1)
	if string(s[5:5+l]) != t1 {
		return errors.New("could not connect a Hg Command Server")
	}
	return nil
}

// Close ends the conection with the Mercurial CommandServer.
//
// In fact it's closing the stdin of the Hg CS that closes connection,
// as per the Hg CS documentation.
func Close() error {
	pout.Close()
	// Closing it's stdin is what really closes the Hg Command Server.
	pin.Close()
	err = hgserver.Wait()
	if err != nil {
		return err
	}
	fmt.Println("Disconnected from Hg Command Server at: " + repo)
	return nil
} // Close()
<|endoftext|>


<commit_before>// Copyright (C) 2012, Johan P. P. Samyn <johan.samyn@gmail.com> All rights reserved.
// Use of this source code is governed by the Simplified BSD License
// that can be found in the LICENSE.txt file.

// Package gohg is a Go client library for using the Mercurial dvcs
// via it's Command Server.
//
// For Mercurial see: http://mercurial/selenic.com/wiki.
//
// For the Hg Command Server see: http://mercurial.selenic.com/wiki/CommandServer.
package gohg

import (
	"encoding/binary"
	"bytes"
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"path"
	"path/filepath"
)

var repo string
var err error
var libdir string
var logfile string

var hgserver *exec.Cmd

// The in and out pipe ends are to be considered from the point of view
// of the Hg Command Server instance. So closing pin closes the Hg CS.
var pout io.ReadCloser
var pin io.WriteCloser

type hgMsg struct {
	Ch   string
	Ln   uint
	Data string
}

type hgCmd struct {
	Cmd  string
	Ln   uint
	Args string
}

// init takes care of some householding, namely preparing a logfile where
// all communication between this lib and the Hg CS can be logged.
func init() {
	var exedir string
	exedir = path.Dir(os.Args[0])
	exedir, err = filepath.Abs(exedir)
	if err != nil {
		log.Fatal("Could not determine path for the gohg.log logfile.")
	}
	logfile = exedir + string(os.PathSeparator) + "gohg.log"
} // init()

// Connect establishes the connection with the Mercurial CommandServer.
//
// If anything went wrong when trying to make the connection,
// an error is returned.
//
// Parameters:
//		hg			The command to run mercurial. Optional.
//		reponame	The folder of the Hg repository to work on. Optional.
//					When blanc the folder where the program is run is used
//					(see locateRepository).
//		config		Configuration settings that will be added to the necessary
//					default/fixed settings (see composeHgConfig() for more).
// Returns an error if the connection could not be established flawlessly.
func Connect(hg string, reponame string, config []string) error {

	// for example:
	// hgserver = exec.Command("M:\\DEV\\hg-stable\\hg",	// the Hg command
	// 		"-R", "C:\\DEV\\go\\src\\golout\\",				// the repo
	// 		"--config", "ui.interactive=True",				// mandatory settings
	// 		"--config", "extensions.color=!",				// more settings (for Windows)
	// 		"serve", "--cmdserver", "pipe")					// start the Command Server

	// Maybe accept a channel as an extra argument for sending the logging to ?
	// And if it's nil, log into a textfile in the folder of this lib.
	// Also do not override that logfile every launch.
	// Maybe even do this in the init() function ?

	if hgserver != nil {
		return errors.New("A Hg Command Server is already connected.")
	}

	if hg == "" {
		// Let the OS determine what Mercurial to run
		// for this machine/user combination.
		hg = "hg"
	}
	// fmt.Printf("hg: %s\n", hg)

	// The Hg Command Server needs a repository.
	repo, err = locateRepository(reponame)
	if err != nil {
		return err
	}
	if repo == "" {
		return errors.New("could not find a Hg repository at: " + reponame)
	}
	// fmt.Printf("repo: %s\n", repo)

	// Maybe we can also offer the possibility of a config file?
	// f.i.: a file gohg.cfg in the same folder as the gohg.exe,
	// and a section per repo, and one "general" section.
	// Or maybe just a [gohg] section in one of the 'normal' Hg config files ?

	var hgconfig []string
	hgconfig = composeHgConfig(hg, repo, config)
	// fmt.Printf("hgconfig: %v\n", hgconfig)

	hgserver = exec.Command(hg)
	hgserver.Args = hgconfig
	hgserver.Dir = repo
	// fmt.Printf("hgserver: %v\n", hgserver)

	pout, err = hgserver.StdoutPipe()
	if err != nil {
		return errors.New("could not connect StdoutPipe: " + err.Error())
	}
	pin, err = hgserver.StdinPipe()
	if err != nil {
		log.Fatal("could not connect StdinPipe: " + err.Error())
	}

	if err := hgserver.Start(); err != nil {
		return errors.New("could not start the Hg Command Server: " + err.Error())
	}

	err = readHelloMessage()
	if err != nil {
		return err
	}

	fmt.Println("Connected with Hg Command Server at: " + repo)

	return nil

} // Connect()

// locateRepository takes care of assuring we have a Merurial repository
// available for working on via the Hg CommandServer, as that is necessary
// to be able to use the Hg CS.
func locateRepository(reponame string) (string, error) {
	repo = reponame
	sep := string(os.PathSeparator)

	// first make a correct path from repo
	repo, err = filepath.Abs(repo)
	if err != nil {
		return "", errors.New(err.Error() +
			"\ncould not find absolute path for: " + repo)
	}
	repo = filepath.Clean(repo)

	// If we do not find a Hg repo in this dir, we search for one
	// up the path, in case we're deeper in it's working copy.
	for {
		_, err = os.Stat(repo + sep + ".hg")
		if err == nil {
			break
		}
		var dir, file string
		dir, file = filepath.Split(repo)
		if dir == "" || file == "" {
			repo = ""
			break
		}
		repo = dir
	}
	if err != nil || repo == "" {
		return "", nil
	}

	return repo, nil

} // locateRepository()

// composeHgConfig handles the different config settings that will be used
// to make the connection with the Hg CS. It concerns settings specially for Hg.
func composeHgConfig(hgcmd string, repo string, config []string) []string {
	var hgconfig []string

	// if len(config) > 0 {
	// 	var cfg string
	// 	for i := 0; i < range(config) {
	// 		cfg = cfg + "," + config[i]
	// 	}
	// 	cmd = cmd + "," + cfg
	// }

	hgconfig = append(hgconfig, hgcmd,
		"-R", repo,
		// These arguments are fixed.
		"--config", "ui.interactive=True",
		"--config", "extensions.color=!",
		"serve", "--cmdserver", "pipe")

	return hgconfig
}

// readHelloMessage reads the special hello message send by the Hg CS.
//
// It has a fixed format, and contains info about the possibilities
// of the Hg CS at hand. It's also a first proof of a working connection.
func readHelloMessage() error {
	const t1 = "capabilities:"
	const t2 = "hg serve [OPTION]"

	s := make([]byte, 1+4+1024)
	_, err = pout.Read(s)
	if err != io.EOF && err != nil {
		return err
	}
	// fmt.Printf("s: %s\n", s)
	if len(s) == 0 {
		return errors.New("no data received from Hg Command Server")
	}
	var ln uint32
	buf := bytes.NewBuffer(s[1:5])
	err = binary.Read(buf, binary.BigEndian, &ln)
	if err != nil {
		fmt.Println("binary.Read failed:", err)
	}
	l := len(t2)
	if string(s[0:l]) == t2 {
		return errors.New("this version of Mercurial does not support the Command Server")
	}
	l = len(t1)
	if string(s[5:5+l]) != t1 {
		return errors.New("could not connect a Hg Command Server")
	}
	return nil
}

// Close ends the conection with the Mercurial CommandServer.
//
// In fact it's closing the stdin of the Hg CS that closes connection,
// as per the Hg CS documentation.
func Close() error {
	pout.Close()
	// Closing it's stdin is what really closes the Hg Command Server.
	pin.Close()
	err = hgserver.Wait()
	if err != nil {
		return err
	}
	fmt.Println("Disconnected from Hg Command Server at: " + repo)
	return nil
} // Close()
<commit_msg>renamed a variable<commit_after>// Copyright (C) 2012, Johan P. P. Samyn <johan.samyn@gmail.com> All rights reserved.
// Use of this source code is governed by the Simplified BSD License
// that can be found in the LICENSE.txt file.

// Package gohg is a Go client library for using the Mercurial dvcs
// via it's Command Server.
//
// For Mercurial see: http://mercurial/selenic.com/wiki.
//
// For the Hg Command Server see: http://mercurial.selenic.com/wiki/CommandServer.
package gohg

import (
	"encoding/binary"
	"bytes"
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"path"
	"path/filepath"
)

var repo string
var err error
var libdir string
var logfile string

var hgserver *exec.Cmd

// The in and out pipe ends are to be considered from the point of view
// of the Hg Command Server instance. So closing pin closes the Hg CS.
var pout io.ReadCloser
var pin io.WriteCloser

type hgMsg struct {
	Ch   string
	Ln   uint
	Data string
}

type hgCmd struct {
	Cmd  string
	Ln   uint
	Args string
}

// init takes care of some householding, namely preparing a logfile where
// all communication between this lib and the Hg CS can be logged.
func init() {
	var exedir string
	exedir = path.Dir(os.Args[0])
	exedir, err = filepath.Abs(exedir)
	if err != nil {
		log.Fatal("Could not determine path for the gohg.log logfile.")
	}
	logfile = exedir + string(os.PathSeparator) + "gohg.log"
} // init()

// Connect establishes the connection with the Mercurial CommandServer.
//
// If anything went wrong when trying to make the connection,
// an error is returned.
//
// Parameters:
//		hg			The command to run mercurial. Optional.
//		reponame	The folder of the Hg repository to work on. Optional.
//					When blanc the folder where the program is run is used
//					(see locateRepository).
//		config		Configuration settings that will be added to the necessary
//					default/fixed settings (see composeHgConfig() for more).
// Returns an error if the connection could not be established flawlessly.
func Connect(hgexe string, reponame string, config []string) error {

	// for example:
	// hgserver = exec.Command("M:\\DEV\\hg-stable\\hg",	// the Hg command
	// 		"-R", "C:\\DEV\\go\\src\\golout\\",				// the repo
	// 		"--config", "ui.interactive=True",				// mandatory settings
	// 		"--config", "extensions.color=!",				// more settings (for Windows)
	// 		"serve", "--cmdserver", "pipe")					// start the Command Server

	// Maybe accept a channel as an extra argument for sending the logging to ?
	// And if it's nil, log into a textfile in the folder of this lib.
	// Also do not override that logfile every launch.
	// Maybe even do this in the init() function ?

	if hgserver != nil {
		return errors.New("A Hg Command Server is already connected.")
	}

	if hgexe == "" {
		// Let the OS determine what Mercurial to run
		// for this machine/user combination.
		hgexe = "hg"
	}
	// fmt.Printf("hgexe: %s\n", hgexe)

	// The Hg Command Server needs a repository.
	repo, err = locateRepository(reponame)
	if err != nil {
		return err
	}
	if repo == "" {
		return errors.New("could not find a Hg repository at: " + reponame)
	}
	// fmt.Printf("repo: %s\n", repo)

	// Maybe we can also offer the possibility of a config file?
	// f.i.: a file gohg.cfg in the same folder as the gohg.exe,
	// and a section per repo, and one "general" section.
	// Or maybe just a [gohg] section in one of the 'normal' Hg config files ?

	var hgconfig []string
	hgconfig = composeHgConfig(hgexe, repo, config)
	// fmt.Printf("hgconfig: %v\n", hgconfig)

	hgserver = exec.Command(hgexe)
	hgserver.Args = hgconfig
	hgserver.Dir = repo
	// fmt.Printf("hgserver: %v\n", hgserver)

	pout, err = hgserver.StdoutPipe()
	if err != nil {
		return errors.New("could not connect StdoutPipe: " + err.Error())
	}
	pin, err = hgserver.StdinPipe()
	if err != nil {
		log.Fatal("could not connect StdinPipe: " + err.Error())
	}

	if err := hgserver.Start(); err != nil {
		return errors.New("could not start the Hg Command Server: " + err.Error())
	}

	err = readHelloMessage()
	if err != nil {
		return err
	}

	fmt.Println("Connected with Hg Command Server at: " + repo)

	return nil

} // Connect()

// locateRepository takes care of assuring we have a Merurial repository
// available for working on via the Hg CommandServer, as that is necessary
// to be able to use the Hg CS.
func locateRepository(reponame string) (string, error) {
	repo = reponame
	sep := string(os.PathSeparator)

	// first make a correct path from repo
	repo, err = filepath.Abs(repo)
	if err != nil {
		return "", errors.New(err.Error() +
			"\ncould not find absolute path for: " + repo)
	}
	repo = filepath.Clean(repo)

	// If we do not find a Hg repo in this dir, we search for one
	// up the path, in case we're deeper in it's working copy.
	for {
		_, err = os.Stat(repo + sep + ".hg")
		if err == nil {
			break
		}
		var dir, file string
		dir, file = filepath.Split(repo)
		if dir == "" || file == "" {
			repo = ""
			break
		}
		repo = dir
	}
	if err != nil || repo == "" {
		return "", nil
	}

	return repo, nil

} // locateRepository()

// composeHgConfig handles the different config settings that will be used
// to make the connection with the Hg CS. It concerns settings specially for Hg.
func composeHgConfig(hgcmd string, repo string, config []string) []string {
	var hgconfig []string

	// if len(config) > 0 {
	// 	var cfg string
	// 	for i := 0; i < range(config) {
	// 		cfg = cfg + "," + config[i]
	// 	}
	// 	cmd = cmd + "," + cfg
	// }

	hgconfig = append(hgconfig, hgcmd,
		"-R", repo,
		// These arguments are fixed.
		"--config", "ui.interactive=True",
		"--config", "extensions.color=!",
		"serve", "--cmdserver", "pipe")

	return hgconfig
}

// readHelloMessage reads the special hello message send by the Hg CS.
//
// It has a fixed format, and contains info about the possibilities
// of the Hg CS at hand. It's also a first proof of a working connection.
func readHelloMessage() error {
	const t1 = "capabilities:"
	const t2 = "hg serve [OPTION]"

	s := make([]byte, 1+4+1024)
	_, err = pout.Read(s)
	if err != io.EOF && err != nil {
		return err
	}
	// fmt.Printf("s: %s\n", s)
	if len(s) == 0 {
		return errors.New("no data received from Hg Command Server")
	}
	var ln uint32
	buf := bytes.NewBuffer(s[1:5])
	err = binary.Read(buf, binary.BigEndian, &ln)
	if err != nil {
		fmt.Println("binary.Read failed:", err)
	}
	l := len(t2)
	if string(s[0:l]) == t2 {
		return errors.New("this version of Mercurial does not support the Command Server")
	}
	l = len(t1)
	if string(s[5:5+l]) != t1 {
		return errors.New("could not connect a Hg Command Server")
	}
	return nil
}

// Close ends the conection with the Mercurial CommandServer.
//
// In fact it's closing the stdin of the Hg CS that closes connection,
// as per the Hg CS documentation.
func Close() error {
	pout.Close()
	// Closing it's stdin is what really closes the Hg Command Server.
	pin.Close()
	err = hgserver.Wait()
	if err != nil {
		return err
	}
	fmt.Println("Disconnected from Hg Command Server at: " + repo)
	return nil
} // Close()
<|endoftext|>


<commit_before>package goma

import (
	"database/sql"
	"fmt"
	"regexp"
	"strconv"
	"time"
	"reflect"
)

// Goma is sql.DB access wrapper.
type Goma struct {
	*sql.DB
	options Options
}

// QueryArgs sql query args
type QueryArgs map[string]interface{}

// Open is create goma client.
// - database open
func Open(configPath string) (*sql.DB, error) {
	opts, err := NewOptions(configPath)
	if err != nil {
		return nil, err
	}
	return OpenOptions(opts)
}

// OpenOptions is create goma client.
// - database open
func OpenOptions(options Options) (*sql.DB, error) {
	return sql.Open(options.Driver, options.Source())
}

// Close sql.DB close.
func (d *Goma) Close() error {
	d.debugPrintln("goma close")

	err := d.DB.Close()

	return err
}

// MySQLGenerateQuery generate bind args query
func MySQLGenerateQuery(queryString string, args QueryArgs) string {
	if len(args) <= 0 {
		return queryString
	}

	for key, val := range args {
		re := regexp.MustCompile(`\/\* ` + key + ` \*\/.*`)

		replaceWord := ""
		switch val.(type) {
		default:
			switch reflect.TypeOf(val).Kind() {
			case reflect.String:
				replaceWord = "'" + reflect.ValueOf(val).String() + "'"
			}
		case int:
			replaceWord = strconv.Itoa(val.(int))
		case bool:
			if val.(bool) {
				replaceWord = "1"
			} else {
				replaceWord = "0"
			}
		case float32:
			replaceWord = strconv.FormatFloat(float64(val.(float32)), 'f', 3, 32)
		case float64:
			replaceWord = strconv.FormatFloat(val.(float64), 'f', 3, 64)
		case int64:
			replaceWord = strconv.FormatInt(val.(int64), 10)
		case string:
			replaceWord = "'" + val.(string) + "'"
		case []uint8:
			replaceWord = "'" + string(val.([]uint8)) + "'"
			//		case Time:
			//			replaceWord = "'" + val.(Time).Time.Format("15:04:05") + "'"
			//		case Date:
			//			replaceWord = "'" + time.Time(val.(Date)).Format("2006-01-02") + "'"
			//		case Timestamp:
			//			replaceWord = "'" + time.Time(val.(Timestamp)).Format("2006-01-02 15:04:05.999999999") + "'"
			//		case mysql.NullTime:
			//			replaceWord = "'" + val.(mysql.NullTime).Time.Format("2006-01-02 15:04:05.999999999") + "'"
		case time.Time:
			replaceWord = "'" + val.(time.Time).Format("2006-01-02 15:04:05.999999999") + "'"
		}
		queryString = re.ReplaceAllString(queryString, replaceWord)
	}

	return queryString
}

// PostgresGenerateQuery generate bind args query
func PostgresGenerateQuery(queryString string, args QueryArgs) string {
	if len(args) <= 0 {
		return queryString
	}

	for key, val := range args {
		re := regexp.MustCompile(`\/\* ` + key + ` \*\/.*`)

		replaceWord := ""
		switch val.(type) {
		case int:
			replaceWord = strconv.Itoa(val.(int))
		case bool:
			replaceWord = strconv.FormatBool(val.(bool))
		case float32:
			replaceWord = strconv.FormatFloat(float64(val.(float32)), 'f', 3, 32)
		case float64:
			replaceWord = strconv.FormatFloat(val.(float64), 'f', 3, 64)
		case int64:
			replaceWord = strconv.FormatInt(val.(int64), 10)
		case string:
			replaceWord = "'" + val.(string) + "'"
		case []uint8:
			replaceWord = "'" + string(val.([]uint8)) + "'"
			//		case Time:
			//			replaceWord = "'" + val.(Time).Time.Format("15:04:05") + "'"
			//		case Date:
			//			replaceWord = "'" + time.Time(val.(Date)).Format("2006-01-02") + "'"
			//		case Timestamp:
			//			replaceWord = "'" + time.Time(val.(Timestamp)).Format("2006-01-02 15:04:05.999999999") + "'"
			//		case mysql.NullTime:
			//			replaceWord = "'" + val.(mysql.NullTime).Time.Format("2006-01-02 15:04:05.999999999") + "'"
		case time.Time:
			replaceWord = "'" + val.(time.Time).Format("2006-01-02 15:04:05.999999999") + "'"
		}
		queryString = re.ReplaceAllString(queryString, replaceWord)
	}

	return queryString
}

func (d *Goma) debugPrintln(v ...interface{}) {
	if d.options.Debug {
		fmt.Println(v...)
	}
}
<commit_msg>fix ParseTime<commit_after>package goma

import (
	"database/sql"
	"fmt"
	"regexp"
	"strconv"
	"time"
	"reflect"
)

var (
	gomaTimeLayout = "2006-01-02 15:04:05.999999999"
)

// ParseTime will configure goma to convert dates of the given layout
func ParseTime(layout string) {
	gomaTimeLayout = layout
}

// Goma is sql.DB access wrapper.
type Goma struct {
	*sql.DB
	options Options
}

// QueryArgs sql query args
type QueryArgs map[string]interface{}

// Open is create goma client.
// - database open
func Open(configPath string) (*sql.DB, error) {
	opts, err := NewOptions(configPath)
	if err != nil {
		return nil, err
	}
	return OpenOptions(opts)
}

// OpenOptions is create goma client.
// - database open
func OpenOptions(options Options) (*sql.DB, error) {
	return sql.Open(options.Driver, options.Source())
}

// Close sql.DB close.
func (d *Goma) Close() error {
	d.debugPrintln("goma close")

	err := d.DB.Close()

	return err
}

// MySQLGenerateQuery generate bind args query
func MySQLGenerateQuery(queryString string, args QueryArgs) string {
	if len(args) <= 0 {
		return queryString
	}

	for key, val := range args {
		re := regexp.MustCompile(`\/\* ` + key + ` \*\/.*`)

		replaceWord := ""
		switch val.(type) {
		default:
			switch reflect.TypeOf(val).Kind() {
			case reflect.String:
				replaceWord = "'" + reflect.ValueOf(val).String() + "'"
			}
		case int:
			replaceWord = strconv.Itoa(val.(int))
		case bool:
			if val.(bool) {
				replaceWord = "true"
			} else {
				replaceWord = "false"
			}
		case float32:
			replaceWord = strconv.FormatFloat(float64(val.(float32)), 'f', 3, 32)
		case float64:
			replaceWord = strconv.FormatFloat(val.(float64), 'f', 3, 64)
		case int64:
			replaceWord = strconv.FormatInt(val.(int64), 10)
		case string:
			replaceWord = "'" + val.(string) + "'"
		case []uint8:
			replaceWord = "'" + string(val.([]uint8)) + "'"
			//		case Time:
			//			replaceWord = "'" + val.(Time).Time.Format("15:04:05") + "'"
			//		case Date:
			//			replaceWord = "'" + time.Time(val.(Date)).Format("2006-01-02") + "'"
			//		case Timestamp:
			//			replaceWord = "'" + time.Time(val.(Timestamp)).Format("2006-01-02 15:04:05.999999999") + "'"
			//		case mysql.NullTime:
			//			replaceWord = "'" + val.(mysql.NullTime).Time.Format("2006-01-02 15:04:05.999999999") + "'"
		case time.Time:
			replaceWord = "'" + val.(time.Time).Format(gomaTimeLayout) + "'"
		}
		queryString = re.ReplaceAllString(queryString, replaceWord)
	}

	return queryString
}

// PostgresGenerateQuery generate bind args query
func PostgresGenerateQuery(queryString string, args QueryArgs) string {
	if len(args) <= 0 {
		return queryString
	}

	for key, val := range args {
		re := regexp.MustCompile(`\/\* ` + key + ` \*\/.*`)

		replaceWord := ""
		switch val.(type) {
		case int:
			replaceWord = strconv.Itoa(val.(int))
		case bool:
			replaceWord = strconv.FormatBool(val.(bool))
		case float32:
			replaceWord = strconv.FormatFloat(float64(val.(float32)), 'f', 3, 32)
		case float64:
			replaceWord = strconv.FormatFloat(val.(float64), 'f', 3, 64)
		case int64:
			replaceWord = strconv.FormatInt(val.(int64), 10)
		case string:
			replaceWord = "'" + val.(string) + "'"
		case []uint8:
			replaceWord = "'" + string(val.([]uint8)) + "'"
			//		case Time:
			//			replaceWord = "'" + val.(Time).Time.Format("15:04:05") + "'"
			//		case Date:
			//			replaceWord = "'" + time.Time(val.(Date)).Format("2006-01-02") + "'"
			//		case Timestamp:
			//			replaceWord = "'" + time.Time(val.(Timestamp)).Format("2006-01-02 15:04:05.999999999") + "'"
			//		case mysql.NullTime:
			//			replaceWord = "'" + val.(mysql.NullTime).Time.Format("2006-01-02 15:04:05.999999999") + "'"
		case time.Time:
			replaceWord = "'" + val.(time.Time).Format(gomaTimeLayout) + "'"
		}
		queryString = re.ReplaceAllString(queryString, replaceWord)
	}

	return queryString
}

func (d *Goma) debugPrintln(v ...interface{}) {
	if d.options.Debug {
		fmt.Println(v...)
	}
}
<|endoftext|>


<commit_before>// The algorithm implemented here is based on "An O(NP) Sequence Comparison Algorithm"
// by described by Sun Wu, Udi Manber and Gene Myers

package gonp

import (
	"fmt"
	"unicode/utf8"
)

const (
	// SesDelete is manipulaton type of deleting element in SES
	SesDelete SesType = iota
	// SesCommon is manipulaton type of same element in SES
	SesCommon
	// SesAdd is manipulaton type of adding element in SES
	SesAdd
)

// SesType is manipulaton type
type SesType int

// Point is coordinate in edit graph
type Point struct {
	x, y int
}

// PointWithRoute is coordinate in edit graph attached route
type PointWithRoute struct {
	x, y, r int
}

// SesElem is element of SES
type SesElem struct {
	e rune
	t SesType
}

// Diff is context for calculating difference between a and b
type Diff struct {
	a              []rune
	b              []rune
	m, n           int
	ed             int
	lcs            []rune
	ses            []SesElem
	reverse        bool
	path           []int
	onlyEd         bool
	pointWithRoute []PointWithRoute
}

func max(x, y int) int {
	if x < y {
		return y
	}
	return x
}

// New is initializer of Diff
func New(a, b string) *Diff {
	m, n := utf8.RuneCountInString(a), utf8.RuneCountInString(b)
	diff := new(Diff)
	diff.a, diff.b = []rune(a), []rune(b)
	diff.m, diff.n = m, n
	diff.reverse = false
	if m >= n {
		diff.a, diff.b = diff.b, diff.a
		diff.m, diff.n = n, m
		diff.reverse = true
	}
	diff.onlyEd = false
	return diff
}

// OnlyEd enables to calculate only edit distance
func (diff *Diff) OnlyEd() {
	diff.onlyEd = true
}

// Editdistance returns edit distance between a and b
func (diff *Diff) Editdistance() int {
	return diff.ed
}

// Lcs returns LCS (Longest Common Subsequence) between a and b
func (diff *Diff) Lcs() []rune {
	return diff.lcs
}

// Lcs returns LCS (Longest Common Subsequence) string between a and b
func (diff *Diff) LcsString() string {
	return string(diff.lcs)
}

// Ses return SES (Shortest Edit Script) between a and b
func (diff *Diff) Ses() []SesElem {
	return diff.ses
}

// PrintSes prints shortest edit script between a and b
func (diff *Diff) PrintSes() {
	for _, e := range diff.ses {
		switch e.t {
		case SesDelete:
			fmt.Printf("- %c\n", e.e)
		case SesAdd:
			fmt.Printf("+ %c\n", e.e)
		case SesCommon:
			fmt.Printf("  %c\n", e.e)
		}
	}
}

// Compose composes diff between a and b
func (diff *Diff) Compose() {
	fp := make([]int, diff.m+diff.n+3)
	diff.path = make([]int, diff.m+diff.n+3)
	diff.pointWithRoute = make([]PointWithRoute, 0)

	for i := range fp {
		fp[i] = -1
		diff.path[i] = -1
	}

	offset := diff.m + 1
	delta := diff.n - diff.m
	for p := 0; ; p++ {

		for k := -p; k <= delta-1; k++ {
			fp[k+offset] = diff.snake(k, fp[k-1+offset]+1, fp[k+1+offset], offset)
		}

		for k := delta + p; k >= delta+1; k-- {
			fp[k+offset] = diff.snake(k, fp[k-1+offset]+1, fp[k+1+offset], offset)
		}

		fp[delta+offset] = diff.snake(delta, fp[delta-1+offset]+1, fp[delta+1+offset], offset)

		if fp[delta+offset] >= diff.n {
			diff.ed = delta + 2*p
			break
		}
	}

	if diff.onlyEd {
		return
	}

	r := diff.path[delta+offset]
	epc := make([]Point, 0)
	for r != -1 {
		epc = append(epc, Point{x: diff.pointWithRoute[r].x, y: diff.pointWithRoute[r].y})
		r = diff.pointWithRoute[r].r
	}
	diff.recordSeq(epc)
}

func (diff *Diff) snake(k, p, pp, offset int) int {
	r := 0
	if p > pp {
		r = diff.path[k-1+offset]
	} else {
		r = diff.path[k+1+offset]
	}

	y := max(p, pp)
	x := y - k

	for x < diff.m && y < diff.n && diff.a[x] == diff.b[y] {
		x++
		y++
	}

	if !diff.onlyEd {
		diff.path[k+offset] = len(diff.pointWithRoute)
		diff.pointWithRoute = append(diff.pointWithRoute, PointWithRoute{x: x, y: y, r: r})
	}

	return y
}

func (diff *Diff) recordSeq(epc []Point) {
	x, y := 1, 1
	px, py := 0, 0
	for i := len(epc) - 1; i >= 0; i-- {
		for (px < epc[i].x) || (py < epc[i].y) {
			if (epc[i].y - epc[i].x) > (py - px) {
				t := SesAdd
				if diff.reverse {
					t = SesDelete
				}
				diff.ses = append(diff.ses, SesElem{e: diff.b[py], t: t})
				y++
				py++
			} else if epc[i].y-epc[i].x < py-px {
				t := SesDelete
				if diff.reverse {
					t = SesAdd
				}
				diff.ses = append(diff.ses, SesElem{e: diff.a[px], t: t})
				x++
				px++
			} else {
				diff.lcs = append(diff.lcs, diff.a[px])
				diff.ses = append(diff.ses, SesElem{e: diff.a[px], t: SesCommon})
				x++
				y++
				px++
				py++
			}
		}
	}
}
<commit_msg>add *Diff#SprintSes()<commit_after>// The algorithm implemented here is based on "An O(NP) Sequence Comparison Algorithm"
// by described by Sun Wu, Udi Manber and Gene Myers

package gonp

import (
	"bytes"
	"fmt"
	"unicode/utf8"
)

const (
	// SesDelete is manipulaton type of deleting element in SES
	SesDelete SesType = iota
	// SesCommon is manipulaton type of same element in SES
	SesCommon
	// SesAdd is manipulaton type of adding element in SES
	SesAdd
)

// SesType is manipulaton type
type SesType int

// Point is coordinate in edit graph
type Point struct {
	x, y int
}

// PointWithRoute is coordinate in edit graph attached route
type PointWithRoute struct {
	x, y, r int
}

// SesElem is element of SES
type SesElem struct {
	e rune
	t SesType
}

// Diff is context for calculating difference between a and b
type Diff struct {
	a              []rune
	b              []rune
	m, n           int
	ed             int
	lcs            []rune
	ses            []SesElem
	reverse        bool
	path           []int
	onlyEd         bool
	pointWithRoute []PointWithRoute
}

func max(x, y int) int {
	if x < y {
		return y
	}
	return x
}

// New is initializer of Diff
func New(a, b string) *Diff {
	m, n := utf8.RuneCountInString(a), utf8.RuneCountInString(b)
	diff := new(Diff)
	diff.a, diff.b = []rune(a), []rune(b)
	diff.m, diff.n = m, n
	diff.reverse = false
	if m >= n {
		diff.a, diff.b = diff.b, diff.a
		diff.m, diff.n = n, m
		diff.reverse = true
	}
	diff.onlyEd = false
	return diff
}

// OnlyEd enables to calculate only edit distance
func (diff *Diff) OnlyEd() {
	diff.onlyEd = true
}

// Editdistance returns edit distance between a and b
func (diff *Diff) Editdistance() int {
	return diff.ed
}

// Lcs returns LCS (Longest Common Subsequence) between a and b
func (diff *Diff) Lcs() []rune {
	return diff.lcs
}

// Lcs returns LCS (Longest Common Subsequence) string between a and b
func (diff *Diff) LcsString() string {
	return string(diff.lcs)
}

// Ses return SES (Shortest Edit Script) between a and b
func (diff *Diff) Ses() []SesElem {
	return diff.ses
}

// PrintSes prints shortest edit script between a and b
func (diff *Diff) PrintSes() {
	fmt.Print(diff.SprintSes())
}

// SprintSes returns string about shortest edit script between a and b
func (diff *Diff) SprintSes() string {
	buf := bytes.NewBufferString("")
	for _, e := range diff.ses {
		switch e.t {
		case SesDelete:
			fmt.Fprintf(buf, "- %c\n", e.e)
		case SesAdd:
			fmt.Fprintf(buf, "+ %c\n", e.e)
		case SesCommon:
			fmt.Fprintf(buf, "  %c\n", e.e)
		}
	}
	return buf.String()
}

// Compose composes diff between a and b
func (diff *Diff) Compose() {
	fp := make([]int, diff.m+diff.n+3)
	diff.path = make([]int, diff.m+diff.n+3)
	diff.pointWithRoute = make([]PointWithRoute, 0)

	for i := range fp {
		fp[i] = -1
		diff.path[i] = -1
	}

	offset := diff.m + 1
	delta := diff.n - diff.m
	for p := 0; ; p++ {

		for k := -p; k <= delta-1; k++ {
			fp[k+offset] = diff.snake(k, fp[k-1+offset]+1, fp[k+1+offset], offset)
		}

		for k := delta + p; k >= delta+1; k-- {
			fp[k+offset] = diff.snake(k, fp[k-1+offset]+1, fp[k+1+offset], offset)
		}

		fp[delta+offset] = diff.snake(delta, fp[delta-1+offset]+1, fp[delta+1+offset], offset)

		if fp[delta+offset] >= diff.n {
			diff.ed = delta + 2*p
			break
		}
	}

	if diff.onlyEd {
		return
	}

	r := diff.path[delta+offset]
	epc := make([]Point, 0)
	for r != -1 {
		epc = append(epc, Point{x: diff.pointWithRoute[r].x, y: diff.pointWithRoute[r].y})
		r = diff.pointWithRoute[r].r
	}
	diff.recordSeq(epc)
}

func (diff *Diff) snake(k, p, pp, offset int) int {
	r := 0
	if p > pp {
		r = diff.path[k-1+offset]
	} else {
		r = diff.path[k+1+offset]
	}

	y := max(p, pp)
	x := y - k

	for x < diff.m && y < diff.n && diff.a[x] == diff.b[y] {
		x++
		y++
	}

	if !diff.onlyEd {
		diff.path[k+offset] = len(diff.pointWithRoute)
		diff.pointWithRoute = append(diff.pointWithRoute, PointWithRoute{x: x, y: y, r: r})
	}

	return y
}

func (diff *Diff) recordSeq(epc []Point) {
	x, y := 1, 1
	px, py := 0, 0
	for i := len(epc) - 1; i >= 0; i-- {
		for (px < epc[i].x) || (py < epc[i].y) {
			if (epc[i].y - epc[i].x) > (py - px) {
				t := SesAdd
				if diff.reverse {
					t = SesDelete
				}
				diff.ses = append(diff.ses, SesElem{e: diff.b[py], t: t})
				y++
				py++
			} else if epc[i].y-epc[i].x < py-px {
				t := SesDelete
				if diff.reverse {
					t = SesAdd
				}
				diff.ses = append(diff.ses, SesElem{e: diff.a[px], t: t})
				x++
				px++
			} else {
				diff.lcs = append(diff.lcs, diff.a[px])
				diff.ses = append(diff.ses, SesElem{e: diff.a[px], t: SesCommon})
				x++
				y++
				px++
				py++
			}
		}
	}
}
<|endoftext|>