<commit_before>package gh

import (
	"fmt"
	"os"

	"github.com/VonC/godbg/exit"

	"github.com/google/go-github/github"
	"golang.org/x/oauth2"
)

var GHex *exit.Exit
var Client *github.Client

func init() {
	token := os.Getenv("GITHUB_AUTH_TOKEN")
	if token == "" {
		print("!!! No OAuth token. Limited API rate 60 per hour. !!!\n")
		print("Set GITHUB_AUTH_TOKEN environment variable to your GitHub PTA\n\n")
		Client = github.NewClient(nil)
	} else {
		tc := oauth2.NewClient(oauth2.NoContext, oauth2.StaticTokenSource(
			&oauth2.Token{AccessToken: token},
		))
		Client = github.NewClient(tc)
	}
}

type Commit struct {
	*github.Commit
	authorDate string
}

func NewCommit(ghc *github.Commit) *Commit {
	return &Commit{ghc, ""}
}

func (c *Commit) String() string {
	f := ""
	if c.Author != nil {
		f = fmt.Sprintf(" from '%s', date '%s'",
			*c.Author.Name, c.Author.Date.Format("02 Jan 2006"))
	}
	return fmt.Sprintf("commit '%s'%s",
		*c.SHA, f)
}

func (c *Commit) NbParents() int {
	return len(c.Parents)
}

func (c *Commit) AuthorDate() string {
	if c.authorDate != "" {
		return c.authorDate
	}
	if c.Message == nil {
		c.Commit = MustGetCommit(*c.SHA).Commit
	}
	c.authorDate = c.Committer.Date.Format("02 Jan 2006")
	return c.authorDate
}

func (c *Commit) CommitterDate() string {
	return c.Committer.Date.Format("02 Jan 2006")
}

func (c *Commit) FirstParent() *Commit {
	return NewCommit(&c.Parents[0])
}
func (c *Commit) SecondParent() *Commit {
	return NewCommit(&c.Parents[1])
}

func (c *Commit) SameSHA1(c2 *Commit) bool {
	return *c.SHA == *c2.SHA
}

func (c *Commit) SameAuthor(c2 *Commit) bool {
	return *c.Author.Name == *c2.Author.Name
}

func (c *Commit) MessageC() string {
	if c.Message == nil {
		c.Commit = MustGetCommit(*c.SHA).Commit
	}
	return *c.Message
}

func (c *Commit) AuthorName() string {
	return *c.Author.Name
}

func MustGetCommit(sha1 string) *Commit {
	commit, _, err := Client.Git.GetCommit("git", "git", sha1)
	if err != nil {
		fmt.Printf("Unable to get commit '%s': err '%v'\n", sha1, err)
		GHex.Exit(1)
	}
	return NewCommit(commit)
}

func FirstSingleParentCommit(parent *Commit) *Commit {
	var pcommit *github.Commit
	var err error
	for pcommit == nil {
		pcommit, _, err = Client.Git.GetCommit("git", "git", *parent.SHA)
		if err != nil {
			fmt.Printf("Unable to get parent commit '%s': err '%v'\n", parent.SHA, err)
			GHex.Exit(1)
		}
		// fmt.Printf("pcommit '%+v', len %d\n", pcommit, len(pcommit.Parents))
		if len(pcommit.Parents) == 2 {
			parent = NewCommit(&pcommit.Parents[1])
			pcommit = nil
		} else {
			break
		}
	}
	return NewCommit(pcommit)
}

func DisplayRateLimit() {
	rate, _, err := Client.RateLimits()
	if err != nil {
		fmt.Printf("Error fetching rate limit: %#v\n\n", err)
	} else {
		const layout = "15:04pm (MST)"
		tc := rate.Core.Reset.Time
		tcs := fmt.Sprintf("%s", tc.Format(layout))
		ts := rate.Search.Reset.Time
		tss := fmt.Sprintf("%s", ts.Format(layout))
		fmt.Printf("\nAPI Rate Core Limit: %d/%d (reset at %s) - Search Limit: %d/%d (reset at %s)\n",
			rate.Core.Remaining, rate.Core.Limit, tcs,
			rate.Search.Remaining, rate.Search.Limit, tss)
	}
}
<commit_msg>AuthorDate uses date from Author, not Committer<commit_after>package gh

import (
	"fmt"
	"os"

	"github.com/VonC/godbg/exit"

	"github.com/google/go-github/github"
	"golang.org/x/oauth2"
)

var GHex *exit.Exit
var Client *github.Client

func init() {
	token := os.Getenv("GITHUB_AUTH_TOKEN")
	if token == "" {
		print("!!! No OAuth token. Limited API rate 60 per hour. !!!\n")
		print("Set GITHUB_AUTH_TOKEN environment variable to your GitHub PTA\n\n")
		Client = github.NewClient(nil)
	} else {
		tc := oauth2.NewClient(oauth2.NoContext, oauth2.StaticTokenSource(
			&oauth2.Token{AccessToken: token},
		))
		Client = github.NewClient(tc)
	}
}

type Commit struct {
	*github.Commit
	authorDate string
}

func NewCommit(ghc *github.Commit) *Commit {
	return &Commit{ghc, ""}
}

func (c *Commit) String() string {
	f := ""
	if c.Author != nil {
		f = fmt.Sprintf(" from '%s', date '%s'",
			*c.Author.Name, c.Author.Date.Format("02 Jan 2006"))
	}
	return fmt.Sprintf("commit '%s'%s",
		*c.SHA, f)
}

func (c *Commit) NbParents() int {
	return len(c.Parents)
}

func (c *Commit) AuthorDate() string {
	if c.authorDate != "" {
		return c.authorDate
	}
	if c.Message == nil {
		c.Commit = MustGetCommit(*c.SHA).Commit
	}
	c.authorDate = c.Author.Date.Format("02 Jan 2006")
	return c.authorDate
}

func (c *Commit) CommitterDate() string {
	return c.Committer.Date.Format("02 Jan 2006")
}

func (c *Commit) FirstParent() *Commit {
	return NewCommit(&c.Parents[0])
}
func (c *Commit) SecondParent() *Commit {
	return NewCommit(&c.Parents[1])
}

func (c *Commit) SameSHA1(c2 *Commit) bool {
	return *c.SHA == *c2.SHA
}

func (c *Commit) SameAuthor(c2 *Commit) bool {
	return *c.Author.Name == *c2.Author.Name
}

func (c *Commit) MessageC() string {
	if c.Message == nil {
		c.Commit = MustGetCommit(*c.SHA).Commit
	}
	return *c.Message
}

func (c *Commit) AuthorName() string {
	return *c.Author.Name
}

func MustGetCommit(sha1 string) *Commit {
	commit, _, err := Client.Git.GetCommit("git", "git", sha1)
	if err != nil {
		fmt.Printf("Unable to get commit '%s': err '%v'\n", sha1, err)
		GHex.Exit(1)
	}
	return NewCommit(commit)
}

func FirstSingleParentCommit(parent *Commit) *Commit {
	var pcommit *github.Commit
	var err error
	for pcommit == nil {
		pcommit, _, err = Client.Git.GetCommit("git", "git", *parent.SHA)
		if err != nil {
			fmt.Printf("Unable to get parent commit '%s': err '%v'\n", parent.SHA, err)
			GHex.Exit(1)
		}
		// fmt.Printf("pcommit '%+v', len %d\n", pcommit, len(pcommit.Parents))
		if len(pcommit.Parents) == 2 {
			parent = NewCommit(&pcommit.Parents[1])
			pcommit = nil
		} else {
			break
		}
	}
	return NewCommit(pcommit)
}

func DisplayRateLimit() {
	rate, _, err := Client.RateLimits()
	if err != nil {
		fmt.Printf("Error fetching rate limit: %#v\n\n", err)
	} else {
		const layout = "15:04pm (MST)"
		tc := rate.Core.Reset.Time
		tcs := fmt.Sprintf("%s", tc.Format(layout))
		ts := rate.Search.Reset.Time
		tss := fmt.Sprintf("%s", ts.Format(layout))
		fmt.Printf("\nAPI Rate Core Limit: %d/%d (reset at %s) - Search Limit: %d/%d (reset at %s)\n",
			rate.Core.Remaining, rate.Core.Limit, tcs,
			rate.Search.Remaining, rate.Search.Limit, tss)
	}
}
<|endoftext|>


<commit_before>// +build linux

/*
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 util

import (
	"errors"
	"fmt"
	"github.com/golang/glog"
	"path"
	"strconv"
	"strings"
)

// FindMultipathDeviceForDevice given a device name like /dev/sdx, find the devicemapper parent
func (handler *deviceHandler) FindMultipathDeviceForDevice(device string) string {
	io := handler.get_io
	disk, err := findDeviceForPath(device, io)
	if err != nil {
		return ""
	}
	sysPath := "/sys/block/"
	if dirs, err := io.ReadDir(sysPath); err == nil {
		for _, f := range dirs {
			name := f.Name()
			if strings.HasPrefix(name, "dm-") {
				if _, err1 := io.Lstat(sysPath + name + "/slaves/" + disk); err1 == nil {
					return "/dev/" + name
				}
			}
		}
	}
	return ""
}

// findDeviceForPath Find the underlaying disk for a linked path such as /dev/disk/by-path/XXXX or /dev/mapper/XXXX
// will return sdX or hdX etc, if /dev/sdX is passed in then sdX will be returned
func findDeviceForPath(path string, io IoUtil) (string, error) {
	devicePath, err := io.EvalSymlinks(path)
	if err != nil {
		return "", err
	}
	// if path /dev/hdX split into "", "dev", "hdX" then we will
	// return just the last part
	parts := strings.Split(devicePath, "/")
	if len(parts) == 3 && strings.HasPrefix(parts[1], "dev") {
		return parts[2], nil
	}
	return "", errors.New("Illegal path for device " + devicePath)
}

// FindSlaveDevicesOnMultipath given a dm name like /dev/dm-1, find all devices
// which are managed by the devicemapper dm-1.
func (handler *deviceHandler) FindSlaveDevicesOnMultipath(dm string) []string {
	var devices []string
	io := handler.get_io
	// Split path /dev/dm-1 into "", "dev", "dm-1"
	parts := strings.Split(dm, "/")
	if len(parts) != 3 || !strings.HasPrefix(parts[1], "dev") {
		return devices
	}
	disk := parts[2]
	slavesPath := path.Join("/sys/block/", disk, "/slaves/")
	if files, err := io.ReadDir(slavesPath); err == nil {
		for _, f := range files {
			devices = append(devices, path.Join("/dev/", f.Name()))
		}
	}
	return devices
}

// GetISCSIPortalHostMapForTarget given a target iqn, find all the scsi hosts logged into
// that target. Returns a map of iSCSI portals (string) to SCSI host numbers (integers).
// For example: {
//    "192.168.30.7:3260": 2,
//    "192.168.30.8:3260": 3,
// }
func (handler *deviceHandler) GetISCSIPortalHostMapForTarget(targetIqn string) (map[string]int, error) {
	portalHostMap := make(map[string]int)
	io := handler.get_io

	// Iterate over all the iSCSI hosts in sysfs
	sysPath := "/sys/class/iscsi_host"
	hostDirs, err := io.ReadDir(sysPath)
	if err != nil {
		return nil, err
	}
	for _, hostDir := range hostDirs {
		// iSCSI hosts are always of the format "host%d"
		// See drivers/scsi/hosts.c in Linux
		hostName := hostDir.Name()
		if !strings.HasPrefix(hostName, "host") {
			continue
		}
		hostNumber, err := strconv.Atoi(strings.TrimPrefix(hostName, "host"))
		if err != nil {
			glog.Errorf("Could not get number from iSCSI host: %s", hostName)
			continue
		}

		// Iterate over the children of the iscsi_host device
		// We are looking for the associated session
		devicePath := sysPath + "/" + hostName + "/device"
		deviceDirs, err := io.ReadDir(devicePath)
		if err != nil {
			return nil, err
		}
		for _, deviceDir := range deviceDirs {
			// Skip over files that aren't the session
			// Sessions are of the format "session%u"
			// See drivers/scsi/scsi_transport_iscsi.c in Linux
			sessionName := deviceDir.Name()
			if !strings.HasPrefix(sessionName, "session") {
				continue
			}

			sessionPath := devicePath + "/" + sessionName

			// Read the target name for the iSCSI session
			targetNamePath := sessionPath + "/iscsi_session/" + sessionName + "/targetname"
			targetName, err := io.ReadFile(targetNamePath)
			if err != nil {
				return nil, err
			}

			// Ignore hosts that don't matchthe target we were looking for.
			if strings.TrimSpace(string(targetName)) != targetIqn {
				continue
			}

			// Iterate over the children of the iSCSI session looking
			// for the iSCSI connection.
			dirs2, err := io.ReadDir(sessionPath)
			if err != nil {
				return nil, err
			}
			for _, dir2 := range dirs2 {
				// Skip over files that aren't the connection
				// Connections are of the format "connection%d:%u"
				// See drivers/scsi/scsi_transport_iscsi.c in Linux
				dirName := dir2.Name()
				if !strings.HasPrefix(dirName, "connection") {
					continue
				}

				connectionPath := sessionPath + "/" + dirName + "/iscsi_connection/" + dirName

				// Read the current and persistent portal information for the connection.
				addrPath := connectionPath + "/address"
				addr, err := io.ReadFile(addrPath)
				if err != nil {
					return nil, err
				}

				portPath := connectionPath + "/port"
				port, err := io.ReadFile(portPath)
				if err != nil {
					return nil, err
				}

				persistentAddrPath := connectionPath + "/persistent_address"
				persistentAddr, err := io.ReadFile(persistentAddrPath)
				if err != nil {
					return nil, err
				}

				persistentPortPath := connectionPath + "/persistent_port"
				persistentPort, err := io.ReadFile(persistentPortPath)
				if err != nil {
					return nil, err
				}

				// Add entries to the map for both the current and persistent portals
				// pointing to the SCSI host for those connections
				portal := strings.TrimSpace(string(addr)) + ":" +
					strings.TrimSpace(string(port))
				portalHostMap[portal] = hostNumber

				persistentPortal := strings.TrimSpace(string(persistentAddr)) + ":" +
					strings.TrimSpace(string(persistentPort))
				portalHostMap[persistentPortal] = hostNumber
			}
		}
	}

	return portalHostMap, nil
}

// FindDevicesForISCSILun given an iqn, and lun number, find all the devices
// corresponding to that LUN.
func (handler *deviceHandler) FindDevicesForISCSILun(targetIqn string, lun int) ([]string, error) {
	devices := make([]string, 0)
	io := handler.get_io

	// Iterate over all the iSCSI hosts in sysfs
	sysPath := "/sys/class/iscsi_host"
	hostDirs, err := io.ReadDir(sysPath)
	if err != nil {
		return nil, err
	}
	for _, hostDir := range hostDirs {
		// iSCSI hosts are always of the format "host%d"
		// See drivers/scsi/hosts.c in Linux
		hostName := hostDir.Name()
		if !strings.HasPrefix(hostName, "host") {
			continue
		}
		hostNumber, err := strconv.Atoi(strings.TrimPrefix(hostName, "host"))
		if err != nil {
			glog.Errorf("Could not get number from iSCSI host: %s", hostName)
			continue
		}

		// Iterate over the children of the iscsi_host device
		// We are looking for the associated session
		devicePath := sysPath + "/" + hostName + "/device"
		deviceDirs, err := io.ReadDir(devicePath)
		if err != nil {
			return nil, err
		}
		for _, deviceDir := range deviceDirs {
			// Skip over files that aren't the session
			// Sessions are of the format "session%u"
			// See drivers/scsi/scsi_transport_iscsi.c in Linux
			sessionName := deviceDir.Name()
			if !strings.HasPrefix(sessionName, "session") {
				continue
			}

			// Read the target name for the iSCSI session
			targetNamePath := devicePath + "/" + sessionName + "/iscsi_session/" + sessionName + "/targetname"
			targetName, err := io.ReadFile(targetNamePath)
			if err != nil {
				return nil, err
			}

			// Only if the session matches the target we were looking for,
			// add it to the map
			if strings.TrimSpace(string(targetName)) != targetIqn {
				continue
			}

			// The list of block devices on the scsi bus will be in a
			// directory called "target%d:%d:%d".
			// See drivers/scsi/scsi_scan.c in Linux
			// We assume the channel/bus and device/controller are always zero for iSCSI
			targetPath := devicePath + "/" + sessionName + fmt.Sprintf("/target%d:0:0", hostNumber)

			// The block device for a given lun will be "%d:%d:%d:%d" --
			// host:channel:bus:LUN
			blockDevicePath := targetPath + fmt.Sprintf("/%d:0:0:%d", hostNumber, lun)

			// If the LUN doesn't exist on this bus, continue on
			_, err = io.Lstat(blockDevicePath)
			if err != nil {
				continue
			}

			// Read the block directory, there should only be one child --
			// the block device "sd*"
			path := blockDevicePath + "/block"
			dirs, err := io.ReadDir(path)
			if err != nil {
				return nil, err
			}
			if 0 < len(dirs) {
				devices = append(devices, dirs[0].Name())
			}
		}
	}

	return devices, nil
}
<commit_msg>UPSTREAM: 74306: Fix scanning of failed targets<commit_after>// +build linux

/*
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 util

import (
	"errors"
	"fmt"
	"github.com/golang/glog"
	"path"
	"strconv"
	"strings"
)

// FindMultipathDeviceForDevice given a device name like /dev/sdx, find the devicemapper parent
func (handler *deviceHandler) FindMultipathDeviceForDevice(device string) string {
	io := handler.get_io
	disk, err := findDeviceForPath(device, io)
	if err != nil {
		return ""
	}
	sysPath := "/sys/block/"
	if dirs, err := io.ReadDir(sysPath); err == nil {
		for _, f := range dirs {
			name := f.Name()
			if strings.HasPrefix(name, "dm-") {
				if _, err1 := io.Lstat(sysPath + name + "/slaves/" + disk); err1 == nil {
					return "/dev/" + name
				}
			}
		}
	}
	return ""
}

// findDeviceForPath Find the underlaying disk for a linked path such as /dev/disk/by-path/XXXX or /dev/mapper/XXXX
// will return sdX or hdX etc, if /dev/sdX is passed in then sdX will be returned
func findDeviceForPath(path string, io IoUtil) (string, error) {
	devicePath, err := io.EvalSymlinks(path)
	if err != nil {
		return "", err
	}
	// if path /dev/hdX split into "", "dev", "hdX" then we will
	// return just the last part
	parts := strings.Split(devicePath, "/")
	if len(parts) == 3 && strings.HasPrefix(parts[1], "dev") {
		return parts[2], nil
	}
	return "", errors.New("Illegal path for device " + devicePath)
}

// FindSlaveDevicesOnMultipath given a dm name like /dev/dm-1, find all devices
// which are managed by the devicemapper dm-1.
func (handler *deviceHandler) FindSlaveDevicesOnMultipath(dm string) []string {
	var devices []string
	io := handler.get_io
	// Split path /dev/dm-1 into "", "dev", "dm-1"
	parts := strings.Split(dm, "/")
	if len(parts) != 3 || !strings.HasPrefix(parts[1], "dev") {
		return devices
	}
	disk := parts[2]
	slavesPath := path.Join("/sys/block/", disk, "/slaves/")
	if files, err := io.ReadDir(slavesPath); err == nil {
		for _, f := range files {
			devices = append(devices, path.Join("/dev/", f.Name()))
		}
	}
	return devices
}

// GetISCSIPortalHostMapForTarget given a target iqn, find all the scsi hosts logged into
// that target. Returns a map of iSCSI portals (string) to SCSI host numbers (integers).
// For example: {
//    "192.168.30.7:3260": 2,
//    "192.168.30.8:3260": 3,
// }
func (handler *deviceHandler) GetISCSIPortalHostMapForTarget(targetIqn string) (map[string]int, error) {
	portalHostMap := make(map[string]int)
	io := handler.get_io

	// Iterate over all the iSCSI hosts in sysfs
	sysPath := "/sys/class/iscsi_host"
	hostDirs, err := io.ReadDir(sysPath)
	if err != nil {
		return nil, err
	}
	for _, hostDir := range hostDirs {
		// iSCSI hosts are always of the format "host%d"
		// See drivers/scsi/hosts.c in Linux
		hostName := hostDir.Name()
		if !strings.HasPrefix(hostName, "host") {
			continue
		}
		hostNumber, err := strconv.Atoi(strings.TrimPrefix(hostName, "host"))
		if err != nil {
			glog.Errorf("Could not get number from iSCSI host: %s", hostName)
			continue
		}

		// Iterate over the children of the iscsi_host device
		// We are looking for the associated session
		devicePath := sysPath + "/" + hostName + "/device"
		deviceDirs, err := io.ReadDir(devicePath)
		if err != nil {
			return nil, err
		}
		for _, deviceDir := range deviceDirs {
			// Skip over files that aren't the session
			// Sessions are of the format "session%u"
			// See drivers/scsi/scsi_transport_iscsi.c in Linux
			sessionName := deviceDir.Name()
			if !strings.HasPrefix(sessionName, "session") {
				continue
			}

			sessionPath := devicePath + "/" + sessionName

			// Read the target name for the iSCSI session
			targetNamePath := sessionPath + "/iscsi_session/" + sessionName + "/targetname"
			targetName, err := io.ReadFile(targetNamePath)
			if err != nil {
				glog.Infof("Failed to process session %s, assuming this session is unavailable: %s", sessionName, err)
				continue
			}

			// Ignore hosts that don't matchthe target we were looking for.
			if strings.TrimSpace(string(targetName)) != targetIqn {
				continue
			}

			// Iterate over the children of the iSCSI session looking
			// for the iSCSI connection.
			dirs2, err := io.ReadDir(sessionPath)
			if err != nil {
				glog.Infof("Failed to process session %s, assuming this session is unavailable: %s", sessionName, err)
				continue
			}
			for _, dir2 := range dirs2 {
				// Skip over files that aren't the connection
				// Connections are of the format "connection%d:%u"
				// See drivers/scsi/scsi_transport_iscsi.c in Linux
				dirName := dir2.Name()
				if !strings.HasPrefix(dirName, "connection") {
					continue
				}

				connectionPath := sessionPath + "/" + dirName + "/iscsi_connection/" + dirName

				// Read the current and persistent portal information for the connection.
				addrPath := connectionPath + "/address"
				addr, err := io.ReadFile(addrPath)
				if err != nil {
					glog.Infof("Failed to process connection %s, assuming this connection is unavailable: %s", dirName, err)
					continue
				}

				portPath := connectionPath + "/port"
				port, err := io.ReadFile(portPath)
				if err != nil {
					glog.Infof("Failed to process connection %s, assuming this connection is unavailable: %s", dirName, err)
					continue
				}

				persistentAddrPath := connectionPath + "/persistent_address"
				persistentAddr, err := io.ReadFile(persistentAddrPath)
				if err != nil {
					glog.Infof("Failed to process connection %s, assuming this connection is unavailable: %s", dirName, err)
					continue
				}

				persistentPortPath := connectionPath + "/persistent_port"
				persistentPort, err := io.ReadFile(persistentPortPath)
				if err != nil {
					glog.Infof("Failed to process connection %s, assuming this connection is unavailable: %s", dirName, err)
					continue
				}

				// Add entries to the map for both the current and persistent portals
				// pointing to the SCSI host for those connections
				portal := strings.TrimSpace(string(addr)) + ":" +
					strings.TrimSpace(string(port))
				portalHostMap[portal] = hostNumber

				persistentPortal := strings.TrimSpace(string(persistentAddr)) + ":" +
					strings.TrimSpace(string(persistentPort))
				portalHostMap[persistentPortal] = hostNumber
			}
		}
	}

	return portalHostMap, nil
}

// FindDevicesForISCSILun given an iqn, and lun number, find all the devices
// corresponding to that LUN.
func (handler *deviceHandler) FindDevicesForISCSILun(targetIqn string, lun int) ([]string, error) {
	devices := make([]string, 0)
	io := handler.get_io

	// Iterate over all the iSCSI hosts in sysfs
	sysPath := "/sys/class/iscsi_host"
	hostDirs, err := io.ReadDir(sysPath)
	if err != nil {
		return nil, err
	}
	for _, hostDir := range hostDirs {
		// iSCSI hosts are always of the format "host%d"
		// See drivers/scsi/hosts.c in Linux
		hostName := hostDir.Name()
		if !strings.HasPrefix(hostName, "host") {
			continue
		}
		hostNumber, err := strconv.Atoi(strings.TrimPrefix(hostName, "host"))
		if err != nil {
			glog.Errorf("Could not get number from iSCSI host: %s", hostName)
			continue
		}

		// Iterate over the children of the iscsi_host device
		// We are looking for the associated session
		devicePath := sysPath + "/" + hostName + "/device"
		deviceDirs, err := io.ReadDir(devicePath)
		if err != nil {
			return nil, err
		}
		for _, deviceDir := range deviceDirs {
			// Skip over files that aren't the session
			// Sessions are of the format "session%u"
			// See drivers/scsi/scsi_transport_iscsi.c in Linux
			sessionName := deviceDir.Name()
			if !strings.HasPrefix(sessionName, "session") {
				continue
			}

			// Read the target name for the iSCSI session
			targetNamePath := devicePath + "/" + sessionName + "/iscsi_session/" + sessionName + "/targetname"
			targetName, err := io.ReadFile(targetNamePath)
			if err != nil {
				return nil, err
			}

			// Only if the session matches the target we were looking for,
			// add it to the map
			if strings.TrimSpace(string(targetName)) != targetIqn {
				continue
			}

			// The list of block devices on the scsi bus will be in a
			// directory called "target%d:%d:%d".
			// See drivers/scsi/scsi_scan.c in Linux
			// We assume the channel/bus and device/controller are always zero for iSCSI
			targetPath := devicePath + "/" + sessionName + fmt.Sprintf("/target%d:0:0", hostNumber)

			// The block device for a given lun will be "%d:%d:%d:%d" --
			// host:channel:bus:LUN
			blockDevicePath := targetPath + fmt.Sprintf("/%d:0:0:%d", hostNumber, lun)

			// If the LUN doesn't exist on this bus, continue on
			_, err = io.Lstat(blockDevicePath)
			if err != nil {
				continue
			}

			// Read the block directory, there should only be one child --
			// the block device "sd*"
			path := blockDevicePath + "/block"
			dirs, err := io.ReadDir(path)
			if err != nil {
				return nil, err
			}
			if 0 < len(dirs) {
				devices = append(devices, dirs[0].Name())
			}
		}
	}

	return devices, nil
}
<|endoftext|>


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

package webpagereplay

// Converts an old archive format to the new format. This file is
// temporary until crbug.com/730036 is fixed) and is used in
// tools/perf/convert_legacy_wpr_archive.

import (
	"bytes"
	"crypto/tls"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"os"
	"strconv"

	"github.com/codegangsta/cli"
)

type ConvertorConfig struct {
	inputFile, outputFile string
	httpPort, httpsPort   int
	keyFile, certFile     string

	// Computed states
	tlsCert  tls.Certificate
	x509Cert *x509.Certificate
}

func (cfg *ConvertorConfig) Flags() []cli.Flag {
	return []cli.Flag{
		cli.StringFlag{
			Name:        "https_cert_file",
			Value:       "wpr_cert.pem",
			Usage:       "File containing a PEM-encoded X509 certificate to use with SSL.",
			Destination: &cfg.certFile,
		},
		cli.StringFlag{
			Name:        "https_key_file",
			Value:       "wpr_key.pem",
			Usage:       "File containing a PEM-encoded private key to use with SSL.",
			Destination: &cfg.keyFile,
		},
		cli.StringFlag{
			Name:        "input_file",
			Destination: &cfg.inputFile,
		},
		cli.StringFlag{
			Name:        "output_file",
			Destination: &cfg.outputFile,
		},
		cli.IntFlag{
			Name:        "https_port",
			Value:       -1,
			Usage:       "Python WPR's https port.",
			Destination: &cfg.httpsPort,
		},
		cli.IntFlag{
			Name:        "http_port",
			Value:       -1,
			Usage:       "Python WPR's http port.",
			Destination: &cfg.httpPort,
		},
	}
}

func (r *ConvertorConfig) recordServerCert(scheme string, serverName string, archive *WritableArchive) error {
	if scheme != "https" {
		return nil
	}
	derBytes, negotiatedProtocol, err := archive.Archive.FindHostTlsConfig(serverName)
	if err == nil && derBytes != nil {
		return err
	}
	derBytes, negotiatedProtocol, err = MintServerCert(serverName, r.x509Cert, r.tlsCert.PrivateKey)
	if err != nil {
		return err
	}
	archive.RecordTlsConfig(serverName, derBytes, negotiatedProtocol)
	return nil
}

func (r *ConvertorConfig) Convert(c *cli.Context) {
	if r.httpPort == -1 || r.httpsPort == -1 {
		fmt.Printf("must provide ports of python WPR server")
		os.Exit(0)
	}
	file, err := ioutil.ReadFile(r.inputFile)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Loading cert from %v\n", r.certFile)
	fmt.Printf("Loading key from %v\n", r.keyFile)
	r.tlsCert, err = tls.LoadX509KeyPair(r.certFile, r.keyFile)
	if err != nil {
		panic(fmt.Errorf("error opening cert or key files: %v", err))
	}
	r.x509Cert, err = x509.ParseCertificate(r.tlsCert.Certificate[0])
	if err != nil {
		panic(err)
	}
	transport := http.Transport{
		Dial: func(network, addr string) (net.Conn, error) {
			return net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", r.httpPort))
		},
		DialTLS: func(network, addr string) (net.Conn, error) {
			return tls.Dial(network,
				fmt.Sprintf("127.0.0.1:%d", r.httpsPort),
				&tls.Config{InsecureSkipVerify: true})
		},
	}
	archive, err := OpenWritableArchive(r.outputFile)
	if err != nil {
		panic(fmt.Errorf("cannot open: %v", err))
	}
	type JsonHeader struct {
		Key, Val string
	}
	type JsonRequest struct {
		Headers []JsonHeader
		Method  string
		Url     string
		Body    string
	}

	var requests []JsonRequest
	err = json.Unmarshal(file, &requests)
	if err != nil {
		panic(err)
	}
	for _, req := range requests {
		url, err := url.Parse(req.Url)
		if err != nil {
			panic(fmt.Errorf("failed: %v", err))
		}
		fmt.Printf("%v\n", url)
		reqHeaders := http.Header{}
		for _, h := range req.Headers {
			reqHeaders.Set(h.Key, h.Val)
			fmt.Printf("%s: %s\n", h.Key, h.Val)
		}
		httpReq := http.Request{Method: req.Method, URL: url}
		fmt.Printf("reqHeader %v\n", reqHeaders)
		var requestBody []byte
		if len(req.Body) == 0 {
			httpReq.ContentLength = 0
			reqHeaders.Set("content-length", "0")
		} else {
			requestBody, err = base64.StdEncoding.DecodeString(req.Body)
			if err != nil {
				panic(err)
			}
			httpReq.ContentLength = int64(len(requestBody))
			reqHeaders.Set("content-length", strconv.Itoa(len(requestBody)))
			httpReq.Body = ioutil.NopCloser(bytes.NewReader(requestBody))
		}
		httpReq.Host = url.Host
		httpReq.Header = reqHeaders
		httpReq.Proto = "HTTP/1.1"
		httpReq.ProtoMajor = 1
		httpReq.ProtoMinor = 1
		var resp *http.Response
		resp, err = transport.RoundTrip(&httpReq)
		if err != nil {
			panic(fmt.Errorf("RoundTrip failed: %v", err))
		}

		responseBody, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			panic(fmt.Errorf("warning: origin response truncated: %v", err))
		}
		resp.Body.Close()
		fmt.Printf("status: %d\n", resp.StatusCode)
		if requestBody != nil {
			httpReq.Body = ioutil.NopCloser(bytes.NewReader(requestBody))
		}
		resp.Body = ioutil.NopCloser(bytes.NewReader(responseBody))
		if err := archive.RecordRequest(url.Scheme, &httpReq, resp); err != nil {
			panic(fmt.Sprintf("failed recording request: %v", err))
		}
		if err := r.recordServerCert(url.Scheme, url.Host, archive); err != nil {
			// If cert fails to record, it usually because the host
			// is no longer reachable. Do not error out here.
			fmt.Printf("failed recording cert: %v", err)
		}
	}

	if err := archive.Close(); err != nil {
		fmt.Printf("Error flushing archive: %v", err)
	}
}
<commit_msg>[wpr-go] Use a dummy cert when legacyformatconverter.go fails to reach server<commit_after>// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package webpagereplay

// Converts an old archive format to the new format. This file is
// temporary until crbug.com/730036 is fixed) and is used in
// tools/perf/convert_legacy_wpr_archive.

import (
	"bytes"
	"crypto"
	"crypto/rand"
	"crypto/tls"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"os"
	"strconv"

	"github.com/codegangsta/cli"
)

type ConvertorConfig struct {
	inputFile, outputFile string
	httpPort, httpsPort   int
	keyFile, certFile     string

	// Computed states
	tlsCert  tls.Certificate
	x509Cert *x509.Certificate
}

func (cfg *ConvertorConfig) Flags() []cli.Flag {
	return []cli.Flag{
		cli.StringFlag{
			Name:        "https_cert_file",
			Value:       "wpr_cert.pem",
			Usage:       "File containing a PEM-encoded X509 certificate to use with SSL.",
			Destination: &cfg.certFile,
		},
		cli.StringFlag{
			Name:        "https_key_file",
			Value:       "wpr_key.pem",
			Usage:       "File containing a PEM-encoded private key to use with SSL.",
			Destination: &cfg.keyFile,
		},
		cli.StringFlag{
			Name:        "input_file",
			Destination: &cfg.inputFile,
		},
		cli.StringFlag{
			Name:        "output_file",
			Destination: &cfg.outputFile,
		},
		cli.IntFlag{
			Name:        "https_port",
			Value:       -1,
			Usage:       "Python WPR's https port.",
			Destination: &cfg.httpsPort,
		},
		cli.IntFlag{
			Name:        "http_port",
			Value:       -1,
			Usage:       "Python WPR's http port.",
			Destination: &cfg.httpPort,
		},
	}
}

// Mints a dummy server cert to be used when the real server is not reachable.
// This is used in the transition from the python wpr format to the new wprgo format where servers
// from the old recordings (especially CDNs) have since become unreachable. crbug.com/730036
func mintDummyCertificate(serverName string, rootCert *x509.Certificate, rootKey crypto.PrivateKey) ([]byte, string, error) {
	template := rootCert
	if ip := net.ParseIP(serverName); ip != nil {
		template.IPAddresses = []net.IP{ip}
	} else {
		template.DNSNames = []string{serverName}
	}
	var buf [20]byte
	if _, err := io.ReadFull(rand.Reader, buf[:]); err != nil {
		return nil, "", fmt.Errorf("create cert failed: %v", err)
	}
	template.SerialNumber.SetBytes(buf[:])
	template.Issuer = template.Subject
	derBytes, err := x509.CreateCertificate(rand.Reader, template, template, template.PublicKey, rootKey)
	if err != nil {
		return nil, "", fmt.Errorf("create cert failed: %v", err)
	}
	return derBytes, "", err
}

func (r *ConvertorConfig) recordServerCert(scheme string, serverName string, archive *WritableArchive) error {
	if scheme != "https" {
		return nil
	}
	derBytes, negotiatedProtocol, err := archive.Archive.FindHostTlsConfig(serverName)
	if err == nil && derBytes != nil {
		return err
	}
	derBytes, negotiatedProtocol, err = MintServerCert(serverName, r.x509Cert, r.tlsCert.PrivateKey)
	if err != nil {
		derBytes, negotiatedProtocol, err = mintDummyCertificate(serverName, r.x509Cert, r.tlsCert.PrivateKey)
		if err != nil {
			return err
		}
	}
	archive.RecordTlsConfig(serverName, derBytes, negotiatedProtocol)
	return nil
}

func (r *ConvertorConfig) Convert(c *cli.Context) {
	if r.httpPort == -1 || r.httpsPort == -1 {
		fmt.Printf("must provide ports of python WPR server")
		os.Exit(0)
	}
	file, err := ioutil.ReadFile(r.inputFile)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Loading cert from %v\n", r.certFile)
	fmt.Printf("Loading key from %v\n", r.keyFile)
	r.tlsCert, err = tls.LoadX509KeyPair(r.certFile, r.keyFile)
	if err != nil {
		panic(fmt.Errorf("error opening cert or key files: %v", err))
	}
	r.x509Cert, err = x509.ParseCertificate(r.tlsCert.Certificate[0])
	if err != nil {
		panic(err)
	}
	transport := http.Transport{
		Dial: func(network, addr string) (net.Conn, error) {
			return net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", r.httpPort))
		},
		DialTLS: func(network, addr string) (net.Conn, error) {
			return tls.Dial(network,
				fmt.Sprintf("127.0.0.1:%d", r.httpsPort),
				&tls.Config{InsecureSkipVerify: true})
		},
	}
	archive, err := OpenWritableArchive(r.outputFile)
	if err != nil {
		panic(fmt.Errorf("cannot open: %v", err))
	}
	type JsonHeader struct {
		Key, Val string
	}
	type JsonRequest struct {
		Headers []JsonHeader
		Method  string
		Url     string
		Body    string
	}

	var requests []JsonRequest
	err = json.Unmarshal(file, &requests)
	if err != nil {
		panic(err)
	}
	for _, req := range requests {
		url, err := url.Parse(req.Url)
		if err != nil {
			panic(fmt.Errorf("failed: %v", err))
		}
		fmt.Printf("%v\n", url)
		reqHeaders := http.Header{}
		for _, h := range req.Headers {
			reqHeaders.Set(h.Key, h.Val)
			fmt.Printf("%s: %s\n", h.Key, h.Val)
		}
		httpReq := http.Request{Method: req.Method, URL: url}
		fmt.Printf("reqHeader %v\n", reqHeaders)
		var requestBody []byte
		if len(req.Body) == 0 {
			httpReq.ContentLength = 0
			reqHeaders.Set("content-length", "0")
		} else {
			requestBody, err = base64.StdEncoding.DecodeString(req.Body)
			if err != nil {
				panic(err)
			}
			httpReq.ContentLength = int64(len(requestBody))
			reqHeaders.Set("content-length", strconv.Itoa(len(requestBody)))
			httpReq.Body = ioutil.NopCloser(bytes.NewReader(requestBody))
		}
		httpReq.Host = url.Host
		httpReq.Header = reqHeaders
		httpReq.Proto = "HTTP/1.1"
		httpReq.ProtoMajor = 1
		httpReq.ProtoMinor = 1
		var resp *http.Response
		resp, err = transport.RoundTrip(&httpReq)
		if err != nil {
			panic(fmt.Errorf("RoundTrip failed: %v", err))
		}

		responseBody, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			panic(fmt.Errorf("warning: origin response truncated: %v", err))
		}
		resp.Body.Close()
		fmt.Printf("status: %d\n", resp.StatusCode)
		if requestBody != nil {
			httpReq.Body = ioutil.NopCloser(bytes.NewReader(requestBody))
		}
		resp.Body = ioutil.NopCloser(bytes.NewReader(responseBody))
		if err := archive.RecordRequest(url.Scheme, &httpReq, resp); err != nil {
			panic(fmt.Sprintf("failed recording request: %v", err))
		}
		if err := r.recordServerCert(url.Scheme, url.Host, archive); err != nil {
			// If cert fails to record, it usually because the host
			// is no longer reachable. Do not error out here.
			fmt.Printf("failed recording cert: %v", err)
		}
	}

	if err := archive.Close(); err != nil {
		fmt.Printf("Error flushing archive: %v", err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"strconv"

	"github.com/NebulousLabs/Andromeda/siacore"
)

// TODO: timeouts?
func (e *Environment) setUpHandlers(apiPort uint16) {
	// Web Interface
	http.HandleFunc("/", e.webIndex)
	http.Handle("/lib/", http.StripPrefix("/lib/", http.FileServer(http.Dir("webpages"))))

	// Plaintext API
	http.HandleFunc("/sync", e.syncHandler)
	http.HandleFunc("/mine", e.mineHandler)
	http.HandleFunc("/sendcoins", e.sendHandler)
	http.HandleFunc("/host", e.hostHandler)
	http.HandleFunc("/rent", e.rentHandler)
	http.HandleFunc("/download", e.downloadHandler)
	http.HandleFunc("/save", e.saveHandler)
	http.HandleFunc("/load", e.loadHandler)
	http.HandleFunc("/status", e.statusHandler)
	http.HandleFunc("/stop", e.stopHandler)

	// JSON API
	http.HandleFunc("/json/status", e.jsonStatusHandler)

	stringPort := string(append([]byte(":"), strconv.Itoa(int(apiPort))...)) // there's gotta be a better way to do this
	http.ListenAndServe(stringPort, nil)
}

// jsonStatusHandler responds to a status call with a json object of the status.
func (e *Environment) jsonStatusHandler(w http.ResponseWriter, req *http.Request) {
	status := e.EnvironmentInfo()
	resp, err := json.Marshal(status)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Fprintf(w, "%s", resp)
}

func (e *Environment) stopHandler(w http.ResponseWriter, req *http.Request) {
	// TODO: more graceful shutdown?
	e.Close()
	os.Exit(0)
}

func (e *Environment) syncHandler(w http.ResponseWriter, req *http.Request) {
	// TODO: don't spawn multiple CatchUps
	go e.CatchUp(e.RandomPeer())
	fmt.Fprint(w, "Sync initiated")
}

func (e *Environment) mineHandler(w http.ResponseWriter, req *http.Request) {
	// TODO: start/stop subcommands
	e.ToggleMining()
	if e.Mining() {
		fmt.Fprint(w, "Started mining")
	} else {
		fmt.Fprint(w, "Stopped mining")
	}
}

func (e *Environment) sendHandler(w http.ResponseWriter, req *http.Request) {
	// Scan the inputs.
	var amount, fee siacore.Currency
	var destBytes []byte
	var dest siacore.CoinAddress
	_, err := fmt.Sscan(req.FormValue("amount"), &amount)
	if err != nil {
		fmt.Fprint(w, err)
		return
	}
	_, err = fmt.Sscan(req.FormValue("fee"), &fee)
	if err != nil {
		fmt.Fprint(w, err)
		return
	}
	_, err = fmt.Sscanf(req.FormValue("dest"), "%x", &destBytes)
	if err != nil {
		fmt.Fprint(w, err)
		return
	}

	// Sanity check the address.
	// TODO: Make addresses checksummed or reed-solomon encoded.
	if len(destBytes) != len(dest) {
		fmt.Fprint(w, "address is not sufficiently long")
		return
	}
	copy(dest[:], destBytes)

	// Spend the coins.
	_, err = e.SpendCoins(amount, fee, dest)
	if err != nil {
		fmt.Fprint(w, err)
		return
	}

	fmt.Fprintf(w, "Sent %v coins to %x, with fee of %v", amount, dest, fee)
}

func (e *Environment) hostHandler(w http.ResponseWriter, req *http.Request) {
	var MB uint64
	var price, freezeCoins siacore.Currency
	var freezeBlocks siacore.BlockHeight
	// scan values
	// TODO: check error
	fmt.Sscan(req.FormValue("MB"), &MB)
	fmt.Sscan(req.FormValue("price"), &price)
	fmt.Sscan(req.FormValue("freezeCoins"), &freezeCoins)
	fmt.Sscan(req.FormValue("freezeBlocks"), &freezeBlocks)

	e.SetHostSettings(HostAnnouncement{
		IPAddress:          e.NetAddress(),
		MinFilesize:        1024 * 1024, // 1 MB
		MaxFilesize:        MB * 1024 * 1024,
		MinDuration:        2000,
		MaxDuration:        10000,
		MinChallengeWindow: 250,
		MaxChallengeWindow: 100,
		MinTolerance:       10,
		Price:              price,
		Burn:               price,
		CoinAddress:        e.CoinAddress(),
		// SpendConditions and FreezeIndex handled by HostAnnounceSelf
	})
	_, err := e.HostAnnounceSelf(freezeCoins, freezeBlocks+e.Height(), 10)
	if err != nil {
		fmt.Fprint(w, err)
	} else {
		fmt.Fprint(w, "Announce successful")
	}
}

func (e *Environment) rentHandler(w http.ResponseWriter, req *http.Request) {
	filename := req.FormValue("filename")
	err := e.ClientProposeContract(filename)
	if err != nil {
		fmt.Fprint(w, err)
	} else {
		fmt.Fprint(w, "Upload complete: "+filename)
	}
}

func (e *Environment) downloadHandler(w http.ResponseWriter, req *http.Request) {
	filename := req.FormValue("filename")
	err := e.Download(filename)
	if err != nil {
		fmt.Fprint(w, err)
	} else {
		fmt.Fprint(w, "Download complete: "+filename)
	}
}

func (e *Environment) saveHandler(w http.ResponseWriter, req *http.Request) {
	// TODO: get type
	filename := req.FormValue("filename")
	err := e.SaveCoinAddress(filename)
	if err != nil {
		fmt.Fprint(w, err)
	} else {
		fmt.Fprint(w, "Saved coin address to "+filename)
	}
}

func (e *Environment) loadHandler(w http.ResponseWriter, req *http.Request) {
	// TODO: get type
	filename, friendname := req.FormValue("filename"), req.FormValue("friendname")
	err := e.LoadCoinAddress(filename, friendname)
	if err != nil {
		fmt.Fprint(w, err)
	} else {
		fmt.Fprint(w, "Loaded coin address to "+filename)
	}
}

// TODO: this should probably just return JSON. Leave formatting to the client.
func (e *Environment) statusHandler(w http.ResponseWriter, req *http.Request) {
	// get state info
	info := e.StateInfo()
	// set mining status
	mineStatus := "OFF"
	if e.Mining() {
		mineStatus = "ON"
	}
	// create peer listing
	peers := "\n"
	for _, address := range e.AddressBook() {
		peers += fmt.Sprintf("\t\t%v:%v\n", address.Host, address.Port)
	}
	// create friend listing
	friends := "\n"
	for name, address := range e.FriendMap() {
		friends += fmt.Sprintf("\t\t%v\t%x\n", name, address)
	}
	// write stats to ResponseWriter
	fmt.Fprintf(w, `General Information:

	Mining Status: %s

	Wallet Address: %x
	Wallet Balance: %v

	Current Block Height: %v
	Current Block Target: %v
	Current Block Depth: %v

	Networked Peers: %s

	Friends: %s`,
		mineStatus, e.CoinAddress(), e.WalletBalance(),
		info.Height, info.Target, info.Depth, peers, friends,
	)
}
<commit_msg>add backend support for host announcement<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"strconv"
	"strings"

	"github.com/NebulousLabs/Andromeda/network"
	"github.com/NebulousLabs/Andromeda/siacore"
)

// TODO: timeouts?
func (e *Environment) setUpHandlers(apiPort uint16) {
	// Web Interface
	http.HandleFunc("/", e.webIndex)
	http.Handle("/lib/", http.StripPrefix("/lib/", http.FileServer(http.Dir("webpages"))))

	// Plaintext API
	http.HandleFunc("/sync", e.syncHandler)
	http.HandleFunc("/mine", e.mineHandler)
	http.HandleFunc("/sendcoins", e.sendHandler)
	http.HandleFunc("/host", e.hostHandler)
	http.HandleFunc("/rent", e.rentHandler)
	http.HandleFunc("/download", e.downloadHandler)
	http.HandleFunc("/save", e.saveHandler)
	http.HandleFunc("/load", e.loadHandler)
	http.HandleFunc("/status", e.statusHandler)
	http.HandleFunc("/stop", e.stopHandler)

	// JSON API
	http.HandleFunc("/json/status", e.jsonStatusHandler)

	stringPort := string(append([]byte(":"), strconv.Itoa(int(apiPort))...)) // there's gotta be a better way to do this
	http.ListenAndServe(stringPort, nil)
}

// jsonStatusHandler responds to a status call with a json object of the status.
func (e *Environment) jsonStatusHandler(w http.ResponseWriter, req *http.Request) {
	status := e.EnvironmentInfo()
	resp, err := json.Marshal(status)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Fprintf(w, "%s", resp)
}

func (e *Environment) stopHandler(w http.ResponseWriter, req *http.Request) {
	// TODO: more graceful shutdown?
	e.Close()
	os.Exit(0)
}

func (e *Environment) syncHandler(w http.ResponseWriter, req *http.Request) {
	// TODO: don't spawn multiple CatchUps
	go e.CatchUp(e.RandomPeer())
	fmt.Fprint(w, "Sync initiated")
}

func (e *Environment) mineHandler(w http.ResponseWriter, req *http.Request) {
	// TODO: start/stop subcommands
	e.ToggleMining()
	if e.Mining() {
		fmt.Fprint(w, "Started mining")
	} else {
		fmt.Fprint(w, "Stopped mining")
	}
}

func (e *Environment) sendHandler(w http.ResponseWriter, req *http.Request) {
	// Scan the inputs.
	var amount, fee siacore.Currency
	var destBytes []byte
	var dest siacore.CoinAddress
	_, err := fmt.Sscan(req.FormValue("amount"), &amount)
	if err != nil {
		fmt.Fprint(w, err)
		return
	}
	_, err = fmt.Sscan(req.FormValue("fee"), &fee)
	if err != nil {
		fmt.Fprint(w, err)
		return
	}
	_, err = fmt.Sscanf(req.FormValue("dest"), "%x", &destBytes)
	if err != nil {
		fmt.Fprint(w, err)
		return
	}

	// Sanity check the address.
	// TODO: Make addresses checksummed or reed-solomon encoded.
	if len(destBytes) != len(dest) {
		fmt.Fprint(w, "address is not the right length")
		return
	}
	copy(dest[:], destBytes)

	// Spend the coins.
	_, err = e.SpendCoins(amount, fee, dest)
	if err != nil {
		fmt.Fprint(w, err)
		return
	}

	fmt.Fprintf(w, "Sent %v coins to %x, with fee of %v", amount, dest, fee)
}

func (e *Environment) hostHandler(w http.ResponseWriter, req *http.Request) {
	// Create all of the variables that get scanned in.
	var ipAddress network.NetAddress
	var totalStorage, minFilesize, maxFilesize, minTolerance uint64
	var minDuration, maxDuration, minWindow, maxWindow, freezeDuration siacore.BlockHeight
	var price, burn, freezeCoins siacore.Currency
	var coinAddressBytes []byte
	var coinAddress siacore.CoinAddress

	// Get the ip addres.
	hostAndPort := strings.Split(req.FormValue("ipaddress"), ":")
	if len(hostAndPort) != 2 {
		fmt.Fprint(w, "could not read ip address")
		return
	}
	_, err := fmt.Sscan(hostAndPort[0], &ipAddress.Host)
	if err != nil {
		fmt.Fprint(w, err)
		return
	}
	_, err = fmt.Sscan(hostAndPort[1], &ipAddress.Port)
	if err != nil {
		fmt.Fprint(w, err)
		return
	}

	// Get the integer variables.
	_, err = fmt.Sscan(req.FormValue("totalstorage"), &totalStorage)
	if err != nil {
		fmt.Fprint(w, err)
		return
	}
	_, err = fmt.Sscan(req.FormValue("minfile"), &minFilesize)
	if err != nil {
		fmt.Fprint(w, err)
		return
	}
	_, err = fmt.Sscan(req.FormValue("maxfile"), &maxFilesize)
	if err != nil {
		fmt.Fprint(w, err)
		return
	}
	_, err = fmt.Sscan(req.FormValue("mintolerance"), &minTolerance)
	if err != nil {
		fmt.Fprint(w, err)
		return
	}
	_, err = fmt.Sscan(req.FormValue("minduration"), &minDuration)
	if err != nil {
		fmt.Fprint(w, err)
		return
	}
	_, err = fmt.Sscan(req.FormValue("maxduration"), &maxDuration)
	if err != nil {
		fmt.Fprint(w, err)
		return
	}
	_, err = fmt.Sscan(req.FormValue("minwin"), &minWindow)
	if err != nil {
		fmt.Fprint(w, err)
		return
	}
	_, err = fmt.Sscan(req.FormValue("maxwin"), &maxWindow)
	if err != nil {
		fmt.Fprint(w, err)
		return
	}
	_, err = fmt.Sscan(req.FormValue("freezeduration"), &freezeDuration)
	if err != nil {
		fmt.Fprint(w, err)
		return
	}
	_, err = fmt.Sscan(req.FormValue("price"), &burn)
	if err != nil {
		fmt.Fprint(w, err)
		return
	}
	_, err = fmt.Sscan(req.FormValue("penalty"), &price)
	if err != nil {
		fmt.Fprint(w, err)
		return
	}
	_, err = fmt.Sscan(req.FormValue("freezevolume"), &freezeCoins)
	if err != nil {
		fmt.Fprint(w, err)
		return
	}

	// Get the CoinAddress.
	_, err = fmt.Sscanf(req.FormValue("coinaddress"), "%x", &coinAddressBytes)
	if err != nil {
		fmt.Fprint(w, err)
		return
	}
	if len(coinAddressBytes) != len(coinAddress) {
		fmt.Fprint(w, "coin address is not the right length.")
		return
	}
	copy(coinAddressBytes[:], coinAddress[:])

	// Set the host settings.
	e.SetHostSettings(HostAnnouncement{
		IPAddress:          ipAddress,
		MinFilesize:        minFilesize,
		MaxFilesize:        maxFilesize,
		MinDuration:        minDuration,
		MaxDuration:        maxDuration,
		MinChallengeWindow: minWindow,
		MaxChallengeWindow: maxWindow,
		MinTolerance:       minTolerance,
		Price:              price,
		Burn:               burn,
		CoinAddress:        coinAddress,
		// SpendConditions and FreezeIndex handled by HostAnnounceSelf
	})

	// Make the host announcement.
	_, err = e.HostAnnounceSelf(freezeCoins, freezeDuration+e.Height(), 10)
	if err != nil {
		fmt.Fprint(w, err)
		return
	}

	fmt.Fprint(w, "Update successful")
}

func (e *Environment) rentHandler(w http.ResponseWriter, req *http.Request) {
	filename := req.FormValue("filename")
	err := e.ClientProposeContract(filename)
	if err != nil {
		fmt.Fprint(w, err)
	} else {
		fmt.Fprint(w, "Upload complete: "+filename)
	}
}

func (e *Environment) downloadHandler(w http.ResponseWriter, req *http.Request) {
	filename := req.FormValue("filename")
	err := e.Download(filename)
	if err != nil {
		fmt.Fprint(w, err)
	} else {
		fmt.Fprint(w, "Download complete: "+filename)
	}
}

func (e *Environment) saveHandler(w http.ResponseWriter, req *http.Request) {
	// TODO: get type
	filename := req.FormValue("filename")
	err := e.SaveCoinAddress(filename)
	if err != nil {
		fmt.Fprint(w, err)
	} else {
		fmt.Fprint(w, "Saved coin address to "+filename)
	}
}

func (e *Environment) loadHandler(w http.ResponseWriter, req *http.Request) {
	// TODO: get type
	filename, friendname := req.FormValue("filename"), req.FormValue("friendname")
	err := e.LoadCoinAddress(filename, friendname)
	if err != nil {
		fmt.Fprint(w, err)
	} else {
		fmt.Fprint(w, "Loaded coin address to "+filename)
	}
}

// TODO: this should probably just return JSON. Leave formatting to the client.
func (e *Environment) statusHandler(w http.ResponseWriter, req *http.Request) {
	// get state info
	info := e.StateInfo()
	// set mining status
	mineStatus := "OFF"
	if e.Mining() {
		mineStatus = "ON"
	}
	// create peer listing
	peers := "\n"
	for _, address := range e.AddressBook() {
		peers += fmt.Sprintf("\t\t%v:%v\n", address.Host, address.Port)
	}
	// create friend listing
	friends := "\n"
	for name, address := range e.FriendMap() {
		friends += fmt.Sprintf("\t\t%v\t%x\n", name, address)
	}
	// write stats to ResponseWriter
	fmt.Fprintf(w, `General Information:

	Mining Status: %s

	Wallet Address: %x
	Wallet Balance: %v

	Current Block Height: %v
	Current Block Target: %v
	Current Block Depth: %v

	Networked Peers: %s

	Friends: %s`,
		mineStatus, e.CoinAddress(), e.WalletBalance(),
		info.Height, info.Target, info.Depth, peers, friends,
	)
}
<|endoftext|>


<commit_before>package gron

import (
	"errors"
	"time"

	"github.com/roylee0704/gron/xtime"
)

// Schedule is the interface that wraps the basic Next method.
//
// Next deduces next occuring time based on t and underlying states.
type Schedule interface {
	Next(t time.Time) time.Time
}

// Every returns a Schedule reoccurs every period p, p must be at least
// time.Second.
func Every(p time.Duration) Schedule {

	if p < time.Second {
		p = xtime.Second
	}

	p = p - time.Duration(p.Nanoseconds())%time.Second // truncates up to seconds

	return &periodicSchedule{
		period: p,
	}
}

type periodicSchedule struct {
	period time.Duration
}

// Next adds time t to underlying period, truncates up to unit of seconds.
func (ps periodicSchedule) Next(t time.Time) time.Time {
	return t.Truncate(time.Second).Add(ps.period)
}

// At returns a schedule which reoccurs every period p, at time t(hh:ss).
//
// Note: At panics when period p is less than xtime.Day
func (ps periodicSchedule) At(t string) Schedule {
	if ps.period < xtime.Day {
		panic("period must be at least in days")
	}

	// parse t naively

	return &atSchedule{}
}

// parse naively tokenises hours and seconds.
//
// returns error when input format was incorrect.
func parse(hhss string) (hh int, ss int, err error) {

	hh = int(hhss[0]-'0')*10 + int(hhss[1]-'0')
	ss = int(hhss[3]-'0')*10 + int(hhss[4]-'0')

	if hh < 0 || hh > 24 {
		hh, ss = 0, 0
		err = errors.New("invalid hh format")
	}
	if ss < 0 || ss > 59 {
		hh, ss = 0, 0
		err = errors.New("invalid ss format")
	}

	return
}

type atSchedule struct {
	period time.Duration
	hh     int
	ss     int
}

func (as atSchedule) Next(t time.Time) time.Time {
	return time.Time{}
}
<commit_msg>added new interface: atSchedule<commit_after>package gron

import (
	"errors"
	"time"

	"github.com/roylee0704/gron/xtime"
)

// Schedule is the interface that wraps the basic Next method.
//
// Next deduces next occuring time based on t and underlying states.
type Schedule interface {
	Next(t time.Time) time.Time
}

// AtSchedule extends Schedule by enabling periodic-interval & time-specific setup
type AtSchedule interface {
	At(t string) Schedule
	Schedule
}

// Every returns a Schedule reoccurs every period p, p must be at least
// time.Second.
func Every(p time.Duration) AtSchedule {

	if p < time.Second {
		p = xtime.Second
	}

	p = p - time.Duration(p.Nanoseconds())%time.Second // truncates up to seconds

	return &periodicSchedule{
		period: p,
	}
}

type periodicSchedule struct {
	period time.Duration
}

// Next adds time t to underlying period, truncates up to unit of seconds.
func (ps periodicSchedule) Next(t time.Time) time.Time {
	return t.Truncate(time.Second).Add(ps.period)
}

// At returns a schedule which reoccurs every period p, at time t(hh:ss).
//
// Note: At panics when period p is less than xtime.Day, and error hh:ss format.
func (ps periodicSchedule) At(t string) Schedule {
	if ps.period < xtime.Day {
		panic("period must be at least in days")
	}

	// parse t naively
	h, s, err := parse(t)

	if err != nil {
		panic(err.Error())
	}

	return &atSchedule{
		period: ps.period,
		hh:     h,
		mm:     s,
	}
}

// parse naively tokenises hours and seconds.
//
// returns error when input format was incorrect.
func parse(hhmm string) (hh int, mm int, err error) {

	hh = int(hhmm[0]-'0')*10 + int(hhmm[1]-'0')
	mm = int(hhmm[3]-'0')*10 + int(hhmm[4]-'0')

	if hh < 0 || hh > 24 {
		hh, mm = 0, 0
		err = errors.New("invalid hh format")
	}
	if mm < 0 || mm > 59 {
		hh, mm = 0, 0
		err = errors.New("invalid mm format")
	}

	return
}

type atSchedule struct {
	period time.Duration
	hh     int
	mm     int
}

// reset returns new Date based on time instant t, and reconfigure its hh:ss
// according to atSchedule's hh:ss.
func (as atSchedule) reset(t time.Time) time.Time {
	return time.Date(t.Year(), t.Month(), t.Day(), as.hh, as.mm, 0, 0, time.UTC)
}

// Next returns **next** time.
// if t had passed its schedule, returns reset(t) + period, else returns reset(t)
func (as atSchedule) Next(t time.Time) time.Time {

	return time.Time{}
}
<|endoftext|>


<commit_before>/*
 * remove-empty-directories
 *
 * Walks a file system hierarchy and removes all directories with no children.
 */
package main

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/karrick/godirwalk"
)

func main() {
	if len(os.Args) < 2 {
		fmt.Fprintf(os.Stderr, "usage: %s dir1 [dir2 [dir3...]]\n", filepath.Base(os.Args[0]))
		os.Exit(2)
	}

	var count, total int
	var err error

	for _, arg := range os.Args[1:] {
		count, err = pruneEmptyDirectories(arg)
		total += count
		if err != nil {
			break
		}
	}

	fmt.Fprintf(os.Stderr, "Removed %d empty directories\n", total)
	if err != nil {
		fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
		os.Exit(1)
	}
}

func pruneEmptyDirectories(osDirname string) (int, error) {
	var count int

	err := godirwalk.Walk(osDirname, &godirwalk.Options{
		Unsorted: true,
		Callback: func(_ string, _ *godirwalk.Dirent) error {
			// no-op while diving in; all the fun happens in PostChildrenCallback
			return nil
		},
		PostChildrenCallback: func(osPathname string, _ *godirwalk.Dirent) error {
			deChildren, err := godirwalk.ReadDirents(osPathname, nil)
			if err != nil {
				return err
			}
			// NOTE: ReadDirents skips "." and ".."
			if len(deChildren) > 0 {
				return nil // this directory has children; no additional work here
			}
			if osPathname == osDirname {
				return nil // do not remove provided root directory
			}
			err = os.Remove(osPathname)
			if err == nil {
				count++
			}
			return err
		},
	})

	return count, err
}
<commit_msg>only reads first entry in each directory to check whether it is empty<commit_after>/*
 * remove-empty-directories
 *
 * Walks a file system hierarchy and removes all directories with no children.
 */
package main

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/karrick/godirwalk"
)

func main() {
	if len(os.Args) < 2 {
		fmt.Fprintf(os.Stderr, "usage: %s dir1 [dir2 [dir3...]]\n", filepath.Base(os.Args[0]))
		os.Exit(2)
	}

	var count, total int
	var err error

	for _, arg := range os.Args[1:] {
		count, err = pruneEmptyDirectories(arg)
		total += count
		if err != nil {
			break
		}
	}

	fmt.Fprintf(os.Stderr, "Removed %d empty directories\n", total)
	if err != nil {
		fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
		os.Exit(1)
	}
}

func pruneEmptyDirectories(osDirname string) (int, error) {
	var count int

	err := godirwalk.Walk(osDirname, &godirwalk.Options{
		Unsorted: true,
		Callback: func(_ string, _ *godirwalk.Dirent) error {
			// no-op while diving in; all the fun happens in PostChildrenCallback
			return nil
		},
		PostChildrenCallback: func(osPathname string, _ *godirwalk.Dirent) error {
			s, err := godirwalk.NewScanner(osPathname)
			if err != nil {
				return err
			}

			// Attempt to read only the first directory entry. Remember that
			// Scan skips both "." and ".." entries.
			hasAtLeastOneChild := s.Scan()

			// If error reading from directory, wrap up and return.
			if err := s.Err(); err != nil {
				return err
			}

			if hasAtLeastOneChild {
				return nil // do not remove directory with at least one child
			}
			if osPathname == osDirname {
				return nil // do not remove directory that was provided top-level directory
			}

			err = os.Remove(osPathname)
			if err == nil {
				count++
			}
			return err
		},
	})

	return count, err
}
<|endoftext|>


<commit_before>package twelve

import (
	"fmt"
	"strings"
	"testing"
)

const targetTestVersion = 1

type testCase struct {
	input    int
	expected string
}

var testCases = []testCase{
	{1, "On the first day of Christmas my true love gave to me, a Partridge in a Pear Tree."},
	{2, "On the second day of Christmas my true love gave to me, two Turtle Doves, and a Partridge in a Pear Tree."},
	{3, "On the third day of Christmas my true love gave to me, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."},
	{4, "On the fourth day of Christmas my true love gave to me, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."},
	{5, "On the fifth day of Christmas my true love gave to me, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."},
	{6, "On the sixth day of Christmas my true love gave to me, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."},
	{7, "On the seventh day of Christmas my true love gave to me, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."},
	{8, "On the eighth day of Christmas my true love gave to me, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."},
	{9, "On the ninth day of Christmas my true love gave to me, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."},
	{10, "On the tenth day of Christmas my true love gave to me, ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."},
	{11, "On the eleventh day of Christmas my true love gave to me, eleven Pipers Piping, ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."},
	{12, "On the twelfth day of Christmas my true love gave to me, twelve Drummers Drumming, eleven Pipers Piping, ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."},
}

// diff compares two multi-line strings and returns a helpful comment
func diff(got, want string) string {
	g := strings.Split(got, "\n")
	w := strings.Split(want, "\n")
	for i := 0; ; i++ {
		switch {
		case i < len(g) && i < len(w):
			if g[i] == w[i] {
				continue
			}
			return fmt.Sprintf("-- first difference in line %d:\n"+
				"-- got : %q\n-- want: %q\n", i+1, g[i], w[i])
		case i < len(g):
			return fmt.Sprintf("-- got %d extra lines after line %d:\n"+
				"-- first extra line: %q\n", len(g)-len(w), i, g[i])
		case i < len(w):
			return fmt.Sprintf("-- got %d correct lines, want %d more lines:\n"+
				"-- want next: %q\n", i, len(w)-i, w[i])
		default:
			return "no differences found"
		}
	}
}

func TestSong(t *testing.T) {
	var expected = ""
	for _, test := range testCases {
		expected += test.expected + "\n"
	}
	actual := Song()
	if expected != actual {
		t.Fatalf("Song() =\n%s\n  want:\n%s\n%s", actual, expected, diff(actual, expected))
	}
}

func TestVerse(t *testing.T) {
	for _, test := range testCases {
		actual := Verse(test.input)
		if actual != test.expected {
			t.Errorf("Twelve Days test [%d], expected [%s], actual [%s]", test.input, test.expected, actual)
		}
	}
}

func TestTestVersion(t *testing.T) {
	if testVersion != targetTestVersion {
		t.Errorf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
	}
}
<commit_msg>twelve-days: Ensure test versioning consistency with other exercises (#596)<commit_after>package twelve

import (
	"fmt"
	"strings"
	"testing"
)

const targetTestVersion = 1

type testCase struct {
	input    int
	expected string
}

var testCases = []testCase{
	{1, "On the first day of Christmas my true love gave to me, a Partridge in a Pear Tree."},
	{2, "On the second day of Christmas my true love gave to me, two Turtle Doves, and a Partridge in a Pear Tree."},
	{3, "On the third day of Christmas my true love gave to me, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."},
	{4, "On the fourth day of Christmas my true love gave to me, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."},
	{5, "On the fifth day of Christmas my true love gave to me, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."},
	{6, "On the sixth day of Christmas my true love gave to me, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."},
	{7, "On the seventh day of Christmas my true love gave to me, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."},
	{8, "On the eighth day of Christmas my true love gave to me, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."},
	{9, "On the ninth day of Christmas my true love gave to me, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."},
	{10, "On the tenth day of Christmas my true love gave to me, ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."},
	{11, "On the eleventh day of Christmas my true love gave to me, eleven Pipers Piping, ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."},
	{12, "On the twelfth day of Christmas my true love gave to me, twelve Drummers Drumming, eleven Pipers Piping, ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree."},
}

// diff compares two multi-line strings and returns a helpful comment
func diff(got, want string) string {
	g := strings.Split(got, "\n")
	w := strings.Split(want, "\n")
	for i := 0; ; i++ {
		switch {
		case i < len(g) && i < len(w):
			if g[i] == w[i] {
				continue
			}
			return fmt.Sprintf("-- first difference in line %d:\n"+
				"-- got : %q\n-- want: %q\n", i+1, g[i], w[i])
		case i < len(g):
			return fmt.Sprintf("-- got %d extra lines after line %d:\n"+
				"-- first extra line: %q\n", len(g)-len(w), i, g[i])
		case i < len(w):
			return fmt.Sprintf("-- got %d correct lines, want %d more lines:\n"+
				"-- want next: %q\n", i, len(w)-i, w[i])
		default:
			return "no differences found"
		}
	}
}

func TestTestVersion(t *testing.T) {
	if testVersion != targetTestVersion {
		t.Fatalf("Found testVersion = %v, want %v.", testVersion, targetTestVersion)
	}
}

func TestSong(t *testing.T) {
	var expected = ""
	for _, test := range testCases {
		expected += test.expected + "\n"
	}
	actual := Song()
	if expected != actual {
		t.Fatalf("Song() =\n%s\n  want:\n%s\n%s", actual, expected, diff(actual, expected))
	}
}

func TestVerse(t *testing.T) {
	for _, test := range testCases {
		actual := Verse(test.input)
		if actual != test.expected {
			t.Errorf("Twelve Days test [%d], expected [%s], actual [%s]", test.input, test.expected, actual)
		}
	}
}
<|endoftext|>


<commit_before>package morningStar

import (
	"../jsonHttp"
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"regexp"
	"strconv"
	"sync"
	"time"
)

const urlIds = `https://elasticsearch.vibioh.fr/funds/morningStarId/_search?size=8000`
const urlPerformance = `http://www.morningstar.fr/fr/funds/snapshot/snapshot.aspx?tab=1&id=`
const urlVolatilite = `http://www.morningstar.fr/fr/funds/snapshot/snapshot.aspx?tab=2&id=`
const refreshDelayInHours = 12
const maxConcurrentFetcher = 32

var emptyByte = []byte(``)
var zeroByte = []byte(`0`)
var periodByte = []byte(`.`)
var commaByte = []byte(`,`)
var percentByte = []byte(`%`)
var ampersandByte = []byte(`&`)
var htmAmpersandByte = []byte(`&`)

var requestList = regexp.MustCompile(`^/list$`)
var requestPerf = regexp.MustCompile(`^/(.+?)$`)

var idRegex = regexp.MustCompile(`"_id":"(.*?)"`)
var isinRegex = regexp.MustCompile(`isin.:(\S+)`)
var labelRegex = regexp.MustCompile(`<h1[^>]*?>((?:.|\n)*?)</h1>`)
var ratingRegex = regexp.MustCompile(`<span\sclass=".*?stars([0-9]).*?">`)
var categoryRegex = regexp.MustCompile(`<span[^>]*?>Catégorie</span>.*?<span[^>]*?>(.*?)</span>`)
var perfOneMonthRegex = regexp.MustCompile(`<td[^>]*?>1 mois</td><td[^>]*?>(.*?)</td>`)
var perfThreeMonthRegex = regexp.MustCompile(`<td[^>]*?>3 mois</td><td[^>]*?>(.*?)</td>`)
var perfSixMonthRegex = regexp.MustCompile(`<td[^>]*?>6 mois</td><td[^>]*?>(.*?)</td>`)
var perfOneYearRegex = regexp.MustCompile(`<td[^>]*?>1 an</td><td[^>]*?>(.*?)</td>`)
var volThreeYearRegex = regexp.MustCompile(`<td[^>]*?>Ecart-type 3 ans.?</td><td[^>]*?>(.*?)</td>`)

type performance struct {
	ID            string    `json:"id"`
	Isin          string    `json:"isin"`
	Label         string    `json:"label"`
	Category      string    `json:"category"`
	Rating        string    `json:"rating"`
	OneMonth      float64   `json:"1m"`
	ThreeMonth    float64   `json:"3m"`
	SixMonth      float64   `json:"6m"`
	OneYear       float64   `json:"1y"`
	VolThreeYears float64   `json:"v3y"`
	Score         float64   `json:"score"`
	Update        time.Time `json:"ts"`
}

type syncedMap struct {
	sync.RWMutex
	performances map[string]*performance
}

func (m *syncedMap) get(key string) (*performance, bool) {
	m.RLock()
	defer m.RUnlock()

	perf, ok := m.performances[key]
	return perf, ok
}

func (m *syncedMap) push(key string, performance *performance) {
	m.Lock()
	defer m.Unlock()

	m.performances[key] = performance
}

var performancesCache = syncedMap{performances: make(map[string]*performance)}

type results struct {
	Results interface{} `json:"results"`
}

func init() {
	go func() {
		refreshCache()
		c := time.Tick(refreshDelayInHours * time.Hour)
		for range c {
			refreshCache()
		}
	}()
}

func refreshCache() {
	log.Print(`Cache refresh - start`)
	defer log.Print(`Cache refresh - end`)
	for _, perf := range retrievePerformances(fetchIds(), fetchPerformance) {
		performancesCache.push(perf.ID, perf)
	}
}

func readBody(body io.ReadCloser) ([]byte, error) {
	defer body.Close()
	return ioutil.ReadAll(body)
}

func getBody(url string) ([]byte, error) {
	response, err := http.Get(url)
	if err != nil {
		return nil, fmt.Errorf(`Error while retrieving data from %s: %v`, url, err)
	}

	if response.StatusCode >= 400 {
		return nil, fmt.Errorf(`Got error %d while getting %s`, response.StatusCode, url)
	}

	body, err := readBody(response.Body)
	if err != nil {
		return nil, fmt.Errorf(`Error while reading body of %s: %v`, url, err)
	}

	return body, nil
}

func extractLabel(extract *regexp.Regexp, body []byte, defaultValue []byte) []byte {
	match := extract.FindSubmatch(body)
	if match == nil {
		return defaultValue
	}

	return bytes.Replace(match[1], htmAmpersandByte, ampersandByte, -1)
}

func extractPerformance(extract *regexp.Regexp, body []byte) float64 {
	dotResult := bytes.Replace(extractLabel(extract, body, emptyByte), commaByte, periodByte, -1)
	percentageResult := bytes.Replace(dotResult, percentByte, emptyByte, -1)
	trimResult := bytes.TrimSpace(percentageResult)

	result, err := strconv.ParseFloat(string(trimResult), 64)
	if err != nil {
		return 0.0
	}
	return result
}

func cleanID(morningStarID []byte) string {
	return string(bytes.ToLower(morningStarID))
}

func fetchPerformance(morningStarID []byte) (*performance, error) {
	cleanID := cleanID(morningStarID)
	performanceBody, err := getBody(urlPerformance + cleanID)
	if err != nil {
		return nil, err
	}

	volatiliteBody, err := getBody(urlVolatilite + cleanID)
	if err != nil {
		return nil, err
	}

	isin := string(extractLabel(isinRegex, performanceBody, emptyByte))
	label := string(extractLabel(labelRegex, performanceBody, emptyByte))
	rating := string(extractLabel(ratingRegex, performanceBody, zeroByte))
	category := string(extractLabel(categoryRegex, performanceBody, emptyByte))
	oneMonth := extractPerformance(perfOneMonthRegex, performanceBody)
	threeMonths := extractPerformance(perfThreeMonthRegex, performanceBody)
	sixMonths := extractPerformance(perfSixMonthRegex, performanceBody)
	oneYear := extractPerformance(perfOneYearRegex, performanceBody)
	volThreeYears := extractPerformance(volThreeYearRegex, volatiliteBody)

	score := (0.25 * oneMonth) + (0.3 * threeMonths) + (0.25 * sixMonths) + (0.2 * oneYear) - (0.1 * volThreeYears)
	scoreTruncated := float64(int(score*100)) / 100

	return &performance{cleanID, isin, label, category, rating, oneMonth, threeMonths, sixMonths, oneYear, volThreeYears, scoreTruncated, time.Now()}, nil
}

func fetchIds() [][]byte {
	idsBody, err := getBody(urlIds)
	if err != nil {
		log.Print(err)
		return nil
	}

	idsMatch := idRegex.FindAllSubmatch(idsBody, -1)

	ids := make([][]byte, 0, len(idsMatch))
	for _, match := range idsMatch {
		ids = append(ids, match[1])
	}

	return ids
}

func retrievePerformance(morningStarID []byte) (*performance, error) {
	cleanID := cleanID(morningStarID)

	perf, ok := performancesCache.get(cleanID)
	if ok && time.Now().Add(time.Hour*-(refreshDelayInHours+1)).Before(perf.Update) {
		return perf, nil
	}

	perf, err := fetchPerformance(morningStarID)
	if err != nil {
		return nil, err
	}

	performancesCache.push(cleanID, perf)
	return perf, nil
}

func concurrentRetrievePerformances(ids [][]byte, wg *sync.WaitGroup, performances chan<- *performance, method func([]byte) (*performance, error)) {
	tokens := make(chan int, maxConcurrentFetcher)

	clearSemaphores := func() {
		wg.Done()
		<-tokens
	}

	for _, id := range ids {
		tokens <- 1

		go func(morningStarID []byte) {
			defer clearSemaphores()
			if perf, err := method(morningStarID); err == nil {
				performances <- perf
			}
		}(id)
	}
}

func retrievePerformances(ids [][]byte, method func([]byte) (*performance, error)) []*performance {
	var wg sync.WaitGroup
	wg.Add(len(ids))

	performances := make(chan *performance, maxConcurrentFetcher)
	go concurrentRetrievePerformances(ids, &wg, performances, method)

	go func() {
		wg.Wait()
		close(performances)
	}()

	results := make([]*performance, 0, len(ids))
	for perf := range performances {
		results = append(results, perf)
	}

	return results
}

func performanceHandler(w http.ResponseWriter, morningStarID []byte) {
	perf, err := retrievePerformance(morningStarID)

	if err != nil {
		http.Error(w, err.Error(), 500)
	} else {
		jsonHttp.ResponseJSON(w, *perf)
	}
}

func listHandler(w http.ResponseWriter, r *http.Request) {
	jsonHttp.ResponseJSON(w, results{retrievePerformances(fetchIds(), retrievePerformance)})
}

// Handler for MorningStar request. Should be use with net/http
type Handler struct {
}

func (handler Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Header().Add(`Access-Control-Allow-Origin`, `*`)
	w.Header().Add(`Access-Control-Allow-Headers`, `Content-Type`)
	w.Header().Add(`Access-Control-Allow-Methods`, `GET`)
	w.Header().Add(`X-Content-Type-Options`, `nosniff`)

	urlPath := []byte(r.URL.Path)

	if requestList.Match(urlPath) {
		listHandler(w, r)
	} else if requestPerf.Match(urlPath) {
		performanceHandler(w, requestPerf.FindSubmatch(urlPath)[1])
	}
}
<commit_msg>Changing case<commit_after>package morningStar

import (
	"../jsonHttp"
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"regexp"
	"strconv"
	"sync"
	"time"
)

const urlIds = `https://elasticsearch.vibioh.fr/funds/morningStarId/_search?size=8000`
const urlPerformance = `http://www.morningstar.fr/fr/funds/snapshot/snapshot.aspx?tab=1&id=`
const urlVolatilite = `http://www.morningstar.fr/fr/funds/snapshot/snapshot.aspx?tab=2&id=`
const refreshDelayInHours = 12
const maxConcurrentFetcher = 32

var emptyByte = []byte(``)
var zeroByte = []byte(`0`)
var periodByte = []byte(`.`)
var commaByte = []byte(`,`)
var percentByte = []byte(`%`)
var ampersandByte = []byte(`&`)
var htmAmpersandByte = []byte(`&`)

var requestList = regexp.MustCompile(`^/list$`)
var requestPerf = regexp.MustCompile(`^/(.+?)$`)

var idRegex = regexp.MustCompile(`"_id":"(.*?)"`)
var isinRegex = regexp.MustCompile(`ISIN.:(\S+)`)
var labelRegex = regexp.MustCompile(`<h1[^>]*?>((?:.|\n)*?)</h1>`)
var ratingRegex = regexp.MustCompile(`<span\sclass=".*?stars([0-9]).*?">`)
var categoryRegex = regexp.MustCompile(`<span[^>]*?>Catégorie</span>.*?<span[^>]*?>(.*?)</span>`)
var perfOneMonthRegex = regexp.MustCompile(`<td[^>]*?>1 mois</td><td[^>]*?>(.*?)</td>`)
var perfThreeMonthRegex = regexp.MustCompile(`<td[^>]*?>3 mois</td><td[^>]*?>(.*?)</td>`)
var perfSixMonthRegex = regexp.MustCompile(`<td[^>]*?>6 mois</td><td[^>]*?>(.*?)</td>`)
var perfOneYearRegex = regexp.MustCompile(`<td[^>]*?>1 an</td><td[^>]*?>(.*?)</td>`)
var volThreeYearRegex = regexp.MustCompile(`<td[^>]*?>Ecart-type 3 ans.?</td><td[^>]*?>(.*?)</td>`)

type performance struct {
	ID            string    `json:"id"`
	Isin          string    `json:"isin"`
	Label         string    `json:"label"`
	Category      string    `json:"category"`
	Rating        string    `json:"rating"`
	OneMonth      float64   `json:"1m"`
	ThreeMonth    float64   `json:"3m"`
	SixMonth      float64   `json:"6m"`
	OneYear       float64   `json:"1y"`
	VolThreeYears float64   `json:"v3y"`
	Score         float64   `json:"score"`
	Update        time.Time `json:"ts"`
}

type syncedMap struct {
	sync.RWMutex
	performances map[string]*performance
}

func (m *syncedMap) get(key string) (*performance, bool) {
	m.RLock()
	defer m.RUnlock()

	perf, ok := m.performances[key]
	return perf, ok
}

func (m *syncedMap) push(key string, performance *performance) {
	m.Lock()
	defer m.Unlock()

	m.performances[key] = performance
}

var performancesCache = syncedMap{performances: make(map[string]*performance)}

type results struct {
	Results interface{} `json:"results"`
}

func init() {
	go func() {
		refreshCache()
		c := time.Tick(refreshDelayInHours * time.Hour)
		for range c {
			refreshCache()
		}
	}()
}

func refreshCache() {
	log.Print(`Cache refresh - start`)
	defer log.Print(`Cache refresh - end`)
	for _, perf := range retrievePerformances(fetchIds(), fetchPerformance) {
		performancesCache.push(perf.ID, perf)
	}
}

func readBody(body io.ReadCloser) ([]byte, error) {
	defer body.Close()
	return ioutil.ReadAll(body)
}

func getBody(url string) ([]byte, error) {
	response, err := http.Get(url)
	if err != nil {
		return nil, fmt.Errorf(`Error while retrieving data from %s: %v`, url, err)
	}

	if response.StatusCode >= 400 {
		return nil, fmt.Errorf(`Got error %d while getting %s`, response.StatusCode, url)
	}

	body, err := readBody(response.Body)
	if err != nil {
		return nil, fmt.Errorf(`Error while reading body of %s: %v`, url, err)
	}

	return body, nil
}

func extractLabel(extract *regexp.Regexp, body []byte, defaultValue []byte) []byte {
	match := extract.FindSubmatch(body)
	if match == nil {
		return defaultValue
	}

	return bytes.Replace(match[1], htmAmpersandByte, ampersandByte, -1)
}

func extractPerformance(extract *regexp.Regexp, body []byte) float64 {
	dotResult := bytes.Replace(extractLabel(extract, body, emptyByte), commaByte, periodByte, -1)
	percentageResult := bytes.Replace(dotResult, percentByte, emptyByte, -1)
	trimResult := bytes.TrimSpace(percentageResult)

	result, err := strconv.ParseFloat(string(trimResult), 64)
	if err != nil {
		return 0.0
	}
	return result
}

func cleanID(morningStarID []byte) string {
	return string(bytes.ToLower(morningStarID))
}

func fetchPerformance(morningStarID []byte) (*performance, error) {
	cleanID := cleanID(morningStarID)
	performanceBody, err := getBody(urlPerformance + cleanID)
	if err != nil {
		return nil, err
	}

	volatiliteBody, err := getBody(urlVolatilite + cleanID)
	if err != nil {
		return nil, err
	}

	isin := string(extractLabel(isinRegex, performanceBody, emptyByte))
	label := string(extractLabel(labelRegex, performanceBody, emptyByte))
	rating := string(extractLabel(ratingRegex, performanceBody, zeroByte))
	category := string(extractLabel(categoryRegex, performanceBody, emptyByte))
	oneMonth := extractPerformance(perfOneMonthRegex, performanceBody)
	threeMonths := extractPerformance(perfThreeMonthRegex, performanceBody)
	sixMonths := extractPerformance(perfSixMonthRegex, performanceBody)
	oneYear := extractPerformance(perfOneYearRegex, performanceBody)
	volThreeYears := extractPerformance(volThreeYearRegex, volatiliteBody)

	score := (0.25 * oneMonth) + (0.3 * threeMonths) + (0.25 * sixMonths) + (0.2 * oneYear) - (0.1 * volThreeYears)
	scoreTruncated := float64(int(score*100)) / 100

	return &performance{cleanID, isin, label, category, rating, oneMonth, threeMonths, sixMonths, oneYear, volThreeYears, scoreTruncated, time.Now()}, nil
}

func fetchIds() [][]byte {
	idsBody, err := getBody(urlIds)
	if err != nil {
		log.Print(err)
		return nil
	}

	idsMatch := idRegex.FindAllSubmatch(idsBody, -1)

	ids := make([][]byte, 0, len(idsMatch))
	for _, match := range idsMatch {
		ids = append(ids, match[1])
	}

	return ids
}

func retrievePerformance(morningStarID []byte) (*performance, error) {
	cleanID := cleanID(morningStarID)

	perf, ok := performancesCache.get(cleanID)
	if ok && time.Now().Add(time.Hour*-(refreshDelayInHours+1)).Before(perf.Update) {
		return perf, nil
	}

	perf, err := fetchPerformance(morningStarID)
	if err != nil {
		return nil, err
	}

	performancesCache.push(cleanID, perf)
	return perf, nil
}

func concurrentRetrievePerformances(ids [][]byte, wg *sync.WaitGroup, performances chan<- *performance, method func([]byte) (*performance, error)) {
	tokens := make(chan int, maxConcurrentFetcher)

	clearSemaphores := func() {
		wg.Done()
		<-tokens
	}

	for _, id := range ids {
		tokens <- 1

		go func(morningStarID []byte) {
			defer clearSemaphores()
			if perf, err := method(morningStarID); err == nil {
				performances <- perf
			}
		}(id)
	}
}

func retrievePerformances(ids [][]byte, method func([]byte) (*performance, error)) []*performance {
	var wg sync.WaitGroup
	wg.Add(len(ids))

	performances := make(chan *performance, maxConcurrentFetcher)
	go concurrentRetrievePerformances(ids, &wg, performances, method)

	go func() {
		wg.Wait()
		close(performances)
	}()

	results := make([]*performance, 0, len(ids))
	for perf := range performances {
		results = append(results, perf)
	}

	return results
}

func performanceHandler(w http.ResponseWriter, morningStarID []byte) {
	perf, err := retrievePerformance(morningStarID)

	if err != nil {
		http.Error(w, err.Error(), 500)
	} else {
		jsonHttp.ResponseJSON(w, *perf)
	}
}

func listHandler(w http.ResponseWriter, r *http.Request) {
	jsonHttp.ResponseJSON(w, results{retrievePerformances(fetchIds(), retrievePerformance)})
}

// Handler for MorningStar request. Should be use with net/http
type Handler struct {
}

func (handler Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Header().Add(`Access-Control-Allow-Origin`, `*`)
	w.Header().Add(`Access-Control-Allow-Headers`, `Content-Type`)
	w.Header().Add(`Access-Control-Allow-Methods`, `GET`)
	w.Header().Add(`X-Content-Type-Options`, `nosniff`)

	urlPath := []byte(r.URL.Path)

	if requestList.Match(urlPath) {
		listHandler(w, r)
	} else if requestPerf.Match(urlPath) {
		performanceHandler(w, requestPerf.FindSubmatch(urlPath)[1])
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/gob"
	"fmt"
	"net"
	"os"
	"os/exec"
	"path/filepath"
	debugPkg "runtime/debug"
	"strconv"
	"sync"
	"syscall"
	"time"

	"io"

	linkpkg "github.com/cloudfoundry-incubator/garden-linux/iodaemon/link"
	"github.com/kr/pty"
)

// spawn listens on a unix socket at the given socketPath and when the first connection
// is received, starts a child process.
func spawn(
	socketPath string,
	argv []string,
	timeout time.Duration,
	withTty bool,
	windowColumns int,
	windowRows int,
	debug bool,
	terminate func(int),
	notifyStream io.WriteCloser,
	errStream io.WriteCloser,
) {
	var listener net.Listener

	fatal := func(err error) {
		if debug {
			debugPkg.PrintStack()
		}
		fmt.Fprintln(errStream, "fatal: "+err.Error())
		if listener != nil {
			listener.Close()
		}
		terminate(1)
	}

	if debug {
		enableTracing(socketPath, fatal)
	}

	listener, err := listen(socketPath)
	if err != nil {
		fatal(err)
		return
	}

	executablePath, err := exec.LookPath(argv[0])
	if err != nil {
		fatal(err)
		return
	}

	cmd := child(executablePath, argv)

	var stdinW, stdoutR, stderrR *os.File
	if withTty {
		cmd.Stdin, stdinW, stdoutR, cmd.Stdout, stderrR, cmd.Stderr, err = createTtyPty(windowColumns, windowRows)
		cmd.SysProcAttr.Setctty = true
		cmd.SysProcAttr.Setsid = true
	} else {
		cmd.Stdin, stdinW, stdoutR, cmd.Stdout, stderrR, cmd.Stderr, err = createPipes()
	}

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

	statusR, statusW, err := os.Pipe()
	if err != nil {
		fatal(err)
		return
	}

	notify(notifyStream, "ready")

	childStarted := make(chan bool)
	childTerminated := make(chan bool)
	connectionAccepted := make(chan bool)

	acceptor := func() (net.Conn, error) {
		conn, err := acceptConnection(listener, stdoutR, stderrR, statusR)
		if err != nil {
			fatal(err)
			return nil, err
		}
		return conn, nil
	}

	processConnection := func(conn net.Conn) {
		processLinkRequests(conn, stdinW, cmd, withTty)
	}

	go acceptConnections(acceptor, connectionAccepted, childStarted, processConnection)

	<-connectionAccepted
	go runChildProcess(cmd, notifyStream, statusW, childStarted, childTerminated, fatal)
	<-childTerminated
	errStream.Close()

	listener.Close()
	terminate(0)
}

func acceptConnections(acceptor func() (net.Conn, error), connectionAccepted, childStarted chan bool, processConnection func(net.Conn)) {
	var once sync.Once
	for {
		conn, err := acceptor()
		if err != nil {
			return
		}

		once.Do(func() {
			connectionAccepted <- true
			<-childStarted
		})

		processConnection(conn)
	}
}

func runChildProcess(cmd *exec.Cmd, notifyStream io.WriteCloser, statusW *os.File,
	childStarted, childTerminated chan bool, fatal func(error)) {

	err := cmd.Start()
	if err != nil {
		fatal(err)
		return
	}

	notify(notifyStream, "active")
	notifyStream.Close()

	childStarted <- true

	cmd.Wait()
	if cmd.ProcessState != nil {
		fmt.Fprintf(statusW, "%d\n", cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus())
	}
	childTerminated <- true
}

func notify(notifyStream io.Writer, message string) {
	fmt.Fprintln(notifyStream, message)
}

func enableTracing(socketPath string, fatal func(error)) {
	ownPid := os.Getpid()

	traceOut, err := os.Create(socketPath + ".trace")
	if err != nil {
		fatal(err)
	}

	strace := exec.Command("strace", "-f", "-s", "10240", "-p", strconv.Itoa(ownPid))
	strace.Stdout = traceOut
	strace.Stderr = traceOut

	err = strace.Start()
	if err != nil {
		fatal(err)
	}
}

func listen(socketPath string) (net.Listener, error) {
	// Delete socketPath if it exists to avoid bind failures.
	err := os.Remove(socketPath)
	if err != nil && !os.IsNotExist(err) {
		return nil, err
	}

	err = os.MkdirAll(filepath.Dir(socketPath), 0755)
	if err != nil {
		return nil, err
	}

	return net.Listen("unix", socketPath)
}

func acceptConnection(listener net.Listener, stdoutR, stderrR, statusR *os.File) (net.Conn, error) {
	conn, err := listener.Accept()
	if err != nil {
		return nil, err
	}

	rights := syscall.UnixRights(
		int(stdoutR.Fd()),
		int(stderrR.Fd()),
		int(statusR.Fd()),
	)

	_, _, err = conn.(*net.UnixConn).WriteMsgUnix([]byte{}, rights, nil)
	if err != nil {
		return nil, err
	}

	return conn, nil
}

// Loop receiving and processing link requests on the given connection.
// The loop terminates when the connection is closed or an error occurs.
func processLinkRequests(conn net.Conn, stdinW *os.File, cmd *exec.Cmd, withTty bool) {
	decoder := gob.NewDecoder(conn)

	for {
		var input linkpkg.Input
		err := decoder.Decode(&input)
		if err != nil {
			break
		}

		if input.WindowSize != nil {
			setWinSize(stdinW, input.WindowSize.Columns, input.WindowSize.Rows)
			cmd.Process.Signal(syscall.SIGWINCH)
		} else if input.EOF {
			stdinW.Sync()
			err := stdinW.Close()
			if withTty {
				cmd.Process.Signal(syscall.SIGHUP)
			}
			if err != nil {
				conn.Close()
				break
			}
		} else {
			_, err := stdinW.Write(input.Data)
			if err != nil {
				conn.Close()
				break
			}
		}
	}
}

func createPipes() (stdinR, stdinW, stdoutR, stdoutW, stderrR, stderrW *os.File, err error) {
	// stderr will not be assigned in the case of a tty, so make
	// a dummy pipe to send across instead
	stderrR, stderrW, err = os.Pipe()
	if err != nil {
		return nil, nil, nil, nil, nil, nil, err
	}

	stdinR, stdinW, err = os.Pipe()
	if err != nil {
		return nil, nil, nil, nil, nil, nil, err
	}

	stdoutR, stdoutW, err = os.Pipe()
	if err != nil {
		return nil, nil, nil, nil, nil, nil, err
	}

	return
}

func createTtyPty(windowColumns int, windowRows int) (stdinR, stdinW, stdoutR, stdoutW, stderrR, stderrW *os.File, err error) {
	// stderr will not be assigned in the case of a tty, so ensure it will return EOF on read
	stderrR, err = os.Open("/dev/null")
	if err != nil {
		return nil, nil, nil, nil, nil, nil, err
	}

	pty, tty, err := pty.Open()
	if err != nil {
		return nil, nil, nil, nil, nil, nil, err
	}

	// do NOT assign stderrR to pty; the receiving end should only receive one
	// pty output stream, as they're both the same fd

	stdinW = pty
	stdoutR = pty

	stdinR = tty
	stdoutW = tty
	stderrW = tty

	setWinSize(stdinW, windowColumns, windowRows)

	return
}
<commit_msg>Simplify runChildProcess interface.<commit_after>package main

import (
	"encoding/gob"
	"fmt"
	"net"
	"os"
	"os/exec"
	"path/filepath"
	debugPkg "runtime/debug"
	"strconv"
	"sync"
	"syscall"
	"time"

	"io"

	linkpkg "github.com/cloudfoundry-incubator/garden-linux/iodaemon/link"
	"github.com/kr/pty"
)

// spawn listens on a unix socket at the given socketPath and when the first connection
// is received, starts a child process.
func spawn(
	socketPath string,
	argv []string,
	timeout time.Duration,
	withTty bool,
	windowColumns int,
	windowRows int,
	debug bool,
	terminate func(int),
	notifyStream io.WriteCloser,
	errStream io.WriteCloser,
) {
	var listener net.Listener

	fatal := func(err error) {
		if debug {
			debugPkg.PrintStack()
		}
		fmt.Fprintln(errStream, "fatal: "+err.Error())
		if listener != nil {
			listener.Close()
		}
		terminate(1)
	}

	if debug {
		enableTracing(socketPath, fatal)
	}

	listener, err := listen(socketPath)
	if err != nil {
		fatal(err)
		return
	}

	executablePath, err := exec.LookPath(argv[0])
	if err != nil {
		fatal(err)
		return
	}

	cmd := child(executablePath, argv)

	var stdinW, stdoutR, stderrR *os.File
	if withTty {
		cmd.Stdin, stdinW, stdoutR, cmd.Stdout, stderrR, cmd.Stderr, err = createTtyPty(windowColumns, windowRows)
		cmd.SysProcAttr.Setctty = true
		cmd.SysProcAttr.Setsid = true
	} else {
		cmd.Stdin, stdinW, stdoutR, cmd.Stdout, stderrR, cmd.Stderr, err = createPipes()
	}

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

	statusR, statusW, err := os.Pipe()
	if err != nil {
		fatal(err)
		return
	}

	acceptor := func() (net.Conn, error) {
		conn, err := acceptConnection(listener, stdoutR, stderrR, statusR)
		if err != nil {
			fatal(err)
			return nil, err
		}
		return conn, nil
	}

	processConnection := func(conn net.Conn) {
		processLinkRequests(conn, stdinW, cmd, withTty)
	}

	notify(notifyStream, "ready")

	childStarted := make(chan bool)
	childTerminated := make(chan bool)
	connectionAccepted := make(chan bool)

	go acceptConnections(acceptor, connectionAccepted, childStarted, processConnection)

	startChild := func() error {
		err := cmd.Start()
		if err != nil {
			fatal(err)
			return err
		}

		notify(notifyStream, "active")
		notifyStream.Close()
		return nil
	}

	waitForChild := func() {
		cmd.Wait()
		if cmd.ProcessState != nil {
			fmt.Fprintf(statusW, "%d\n", cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus())
		}
	}

	<-connectionAccepted
	go runChildProcess(startChild, waitForChild, childStarted, childTerminated)

	<-childTerminated
	errStream.Close()
	listener.Close()
	terminate(0)
}

func acceptConnections(acceptor func() (net.Conn, error), connectionAccepted, childStarted chan bool,
	processConnection func(net.Conn)) {
	var once sync.Once
	for {
		conn, err := acceptor()
		if err != nil {
			return
		}

		once.Do(func() {
			connectionAccepted <- true
			<-childStarted
		})

		processConnection(conn)
	}
}

func runChildProcess(startChild func() error, waitForChild func(), childStarted, childTerminated chan bool) {
	err := startChild()
	if err != nil {
		return
	}
	childStarted <- true

	waitForChild()
	childTerminated <- true
}

func notify(notifyStream io.Writer, message string) {
	fmt.Fprintln(notifyStream, message)
}

func enableTracing(socketPath string, fatal func(error)) {
	ownPid := os.Getpid()

	traceOut, err := os.Create(socketPath + ".trace")
	if err != nil {
		fatal(err)
	}

	strace := exec.Command("strace", "-f", "-s", "10240", "-p", strconv.Itoa(ownPid))
	strace.Stdout = traceOut
	strace.Stderr = traceOut

	err = strace.Start()
	if err != nil {
		fatal(err)
	}
}

func listen(socketPath string) (net.Listener, error) {
	// Delete socketPath if it exists to avoid bind failures.
	err := os.Remove(socketPath)
	if err != nil && !os.IsNotExist(err) {
		return nil, err
	}

	err = os.MkdirAll(filepath.Dir(socketPath), 0755)
	if err != nil {
		return nil, err
	}

	return net.Listen("unix", socketPath)
}

func acceptConnection(listener net.Listener, stdoutR, stderrR, statusR *os.File) (net.Conn, error) {
	conn, err := listener.Accept()
	if err != nil {
		return nil, err
	}

	rights := syscall.UnixRights(
		int(stdoutR.Fd()),
		int(stderrR.Fd()),
		int(statusR.Fd()),
	)

	_, _, err = conn.(*net.UnixConn).WriteMsgUnix([]byte{}, rights, nil)
	if err != nil {
		return nil, err
	}

	return conn, nil
}

// Loop receiving and processing link requests on the given connection.
// The loop terminates when the connection is closed or an error occurs.
func processLinkRequests(conn net.Conn, stdinW *os.File, cmd *exec.Cmd, withTty bool) {
	decoder := gob.NewDecoder(conn)

	for {
		var input linkpkg.Input
		err := decoder.Decode(&input)
		if err != nil {
			break
		}

		if input.WindowSize != nil {
			setWinSize(stdinW, input.WindowSize.Columns, input.WindowSize.Rows)
			cmd.Process.Signal(syscall.SIGWINCH)
		} else if input.EOF {
			stdinW.Sync()
			err := stdinW.Close()
			if withTty {
				cmd.Process.Signal(syscall.SIGHUP)
			}
			if err != nil {
				conn.Close()
				break
			}
		} else {
			_, err := stdinW.Write(input.Data)
			if err != nil {
				conn.Close()
				break
			}
		}
	}
}

func createPipes() (stdinR, stdinW, stdoutR, stdoutW, stderrR, stderrW *os.File, err error) {
	// stderr will not be assigned in the case of a tty, so make
	// a dummy pipe to send across instead
	stderrR, stderrW, err = os.Pipe()
	if err != nil {
		return nil, nil, nil, nil, nil, nil, err
	}

	stdinR, stdinW, err = os.Pipe()
	if err != nil {
		return nil, nil, nil, nil, nil, nil, err
	}

	stdoutR, stdoutW, err = os.Pipe()
	if err != nil {
		return nil, nil, nil, nil, nil, nil, err
	}

	return
}

func createTtyPty(windowColumns int, windowRows int) (stdinR, stdinW, stdoutR, stdoutW, stderrR, stderrW *os.File, err error) {
	// stderr will not be assigned in the case of a tty, so ensure it will return EOF on read
	stderrR, err = os.Open("/dev/null")
	if err != nil {
		return nil, nil, nil, nil, nil, nil, err
	}

	pty, tty, err := pty.Open()
	if err != nil {
		return nil, nil, nil, nil, nil, nil, err
	}

	// do NOT assign stderrR to pty; the receiving end should only receive one
	// pty output stream, as they're both the same fd

	stdinW = pty
	stdoutR = pty

	stdinR = tty
	stdoutW = tty
	stderrW = tty

	setWinSize(stdinW, windowColumns, windowRows)

	return
}
<|endoftext|>


<commit_before>package incoming_test

import (
	"os"
	"testing"

	"github.com/lestrrat/roccaforte/incoming"
	"github.com/stretchr/testify/assert"
	"golang.org/x/net/context"
)

var projectID string

func init() {
	projectID = os.Getenv("DATASTORE_PROJECT_ID")
}

func TestGDatastore(t *testing.T) {
	if projectID == "" {
		t.Skip("missing project ID. please set DATASTORE_PROJECT_ID")
		return
	}

	s := incoming.NewGDatastoreStorage(projectID)
	e := incoming.NewEvent(nil, "test.notify")
	if !assert.NoError(t, s.Save(context.Background(), e), "s.Save should succeed") {
		return
	}
	defer s.Delete(e)
}<commit_msg>use a context<commit_after>package incoming_test

import (
	"os"
	"testing"

	"github.com/lestrrat/roccaforte/incoming"
	"github.com/stretchr/testify/assert"
	"golang.org/x/net/context"
)

var projectID string

func init() {
	projectID = os.Getenv("DATASTORE_PROJECT_ID")
}

func TestGDatastore(t *testing.T) {
	if projectID == "" {
		t.Skip("missing project ID. please set DATASTORE_PROJECT_ID")
		return
	}
	ctx := context.Background()
	s := incoming.NewGDatastoreStorage(projectID)
	e := incoming.NewEvent(nil, "test.notify")
	if !assert.NoError(t, s.Save(ctx, e), "s.Save should succeed") {
		return
	}
	defer s.Delete(ctx, e)
}<|endoftext|>