<commit_before>package gps

import (
	"encoding/json"
	"fmt"
	"go/build"
	"os"
	"path/filepath"
	"strings"
	"sync"

	"github.com/Masterminds/semver"
	"github.com/Masterminds/vcs"
)

// Used to compute a friendly filepath from a URL-shaped input
//
// TODO(sdboyer) this is awful. Right?
var sanitizer = strings.NewReplacer(":", "-", "/", "-", "+", "-")

// A SourceManager is responsible for retrieving, managing, and interrogating
// source repositories. Its primary purpose is to serve the needs of a Solver,
// but it is handy for other purposes, as well.
//
// gps's built-in SourceManager, SourceMgr, is intended to be generic and
// sufficient for any purpose. It provides some additional semantics around the
// methods defined here.
type SourceManager interface {
	// RepoExists checks if a repository exists, either upstream or in the
	// SourceManager's central repository cache.
	// TODO(sdboyer) rename to SourceExists
	RepoExists(ProjectIdentifier) (bool, error)

	// ListVersions retrieves a list of the available versions for a given
	// repository name.
	ListVersions(ProjectIdentifier) ([]Version, error)

	// RevisionPresentIn indicates whether the provided Version is present in
	// the given repository.
	RevisionPresentIn(ProjectIdentifier, Revision) (bool, error)

	// ListPackages parses the tree of the Go packages at or below root of the
	// provided ProjectIdentifier, at the provided version.
	ListPackages(ProjectIdentifier, Version) (PackageTree, error)

	// GetManifestAndLock returns manifest and lock information for the provided
	// root import path.
	//
	// gps currently requires that projects be rooted at their repository root,
	// necessitating that the ProjectIdentifier's ProjectRoot must also be a
	// repository root.
	GetManifestAndLock(ProjectIdentifier, Version) (Manifest, Lock, error)

	// ExportProject writes out the tree of the provided import path, at the
	// provided version, to the provided directory.
	ExportProject(ProjectIdentifier, Version, string) error

	// AnalyzerInfo reports the name and version of the logic used to service
	// GetManifestAndLock().
	AnalyzerInfo() (name string, version *semver.Version)
}

// A ProjectAnalyzer is responsible for analyzing a given path for Manifest and
// Lock information. Tools relying on gps must implement one.
type ProjectAnalyzer interface {
	// Perform analysis of the filesystem tree rooted at path, with the
	// root import path importRoot, to determine the project's constraints, as
	// indicated by a Manifest and Lock.
	DeriveManifestAndLock(path string, importRoot ProjectRoot) (Manifest, Lock, error)
	// Report the name and version of this ProjectAnalyzer.
	Info() (name string, version *semver.Version)
}

// SourceMgr is the default SourceManager for gps.
//
// There's no (planned) reason why it would need to be reimplemented by other
// tools; control via dependency injection is intended to be sufficient.
type SourceMgr struct {
	cachedir string
	pms      map[string]*pmState
	pmut     sync.RWMutex
	rr       map[string]struct {
		rr  *remoteRepo
		err error
	}
	rmut sync.RWMutex
	an   ProjectAnalyzer
	ctx  build.Context
}

var _ SourceManager = &SourceMgr{}

// Holds a projectManager, caches of the managed project's data, and information
// about the freshness of those caches
type pmState struct {
	pm   *projectManager
	cf   *os.File // handle for the cache file
	vcur bool     // indicates that we've called ListVersions()
}

// NewSourceManager produces an instance of gps's built-in SourceManager. It
// takes a cache directory (where local instances of upstream repositories are
// stored), a vendor directory for the project currently being worked on, and a
// force flag indicating whether to overwrite the global cache lock file (if
// present).
//
// The returned SourceManager aggressively caches information wherever possible.
// It is recommended that, if tools need to do preliminary, work involving
// upstream repository analysis prior to invoking a solve run, that they create
// this SourceManager as early as possible and use it to their ends. That way,
// the solver can benefit from any caches that may have already been warmed.
//
// gps's SourceManager is intended to be threadsafe (if it's not, please
// file a bug!). It should certainly be safe to reuse from one solving run to
// the next; however, the fact that it takes a basedir as an argument makes it
// much less useful for simultaneous use by separate solvers operating on
// different root projects. This architecture may change in the future.
func NewSourceManager(an ProjectAnalyzer, cachedir string, force bool) (*SourceMgr, error) {
	if an == nil {
		return nil, fmt.Errorf("a ProjectAnalyzer must be provided to the SourceManager")
	}

	err := os.MkdirAll(filepath.Join(cachedir, "sources"), 0777)
	if err != nil {
		return nil, err
	}

	glpath := filepath.Join(cachedir, "sm.lock")
	_, err = os.Stat(glpath)
	if err == nil && !force {
		return nil, fmt.Errorf("cache lock file %s exists - another process crashed or is still running?", glpath)
	}

	_, err = os.OpenFile(glpath, os.O_CREATE|os.O_RDONLY, 0700) // is 0700 sane for this purpose?
	if err != nil {
		return nil, fmt.Errorf("failed to create global cache lock file at %s with err %s", glpath, err)
	}

	ctx := build.Default
	// Replace GOPATH with our cache dir
	ctx.GOPATH = cachedir

	return &SourceMgr{
		cachedir: cachedir,
		pms:      make(map[string]*pmState),
		rr: make(map[string]struct {
			rr  *remoteRepo
			err error
		}),
		ctx: ctx,
		an:  an,
	}, nil
}

// Release lets go of any locks held by the SourceManager.
func (sm *SourceMgr) Release() {
	os.Remove(filepath.Join(sm.cachedir, "sm.lock"))
}

// AnalyzerInfo reports the name and version of the injected ProjectAnalyzer.
func (sm *SourceMgr) AnalyzerInfo() (name string, version *semver.Version) {
	return sm.an.Info()
}

// GetManifestAndLock returns manifest and lock information for the provided
// import path. gps currently requires that projects be rooted at their
// repository root, necessitating that the ProjectIdentifier's ProjectRoot must
// also be a repository root.
//
// The work of producing the manifest and lock is delegated to the injected
// ProjectAnalyzer's DeriveManifestAndLock() method.
func (sm *SourceMgr) GetManifestAndLock(id ProjectIdentifier, v Version) (Manifest, Lock, error) {
	pmc, err := sm.getProjectManager(id)
	if err != nil {
		return nil, nil, err
	}

	return pmc.pm.GetManifestAndLock(id.ProjectRoot, v)
}

// ListPackages parses the tree of the Go packages at and below the ProjectRoot
// of the given ProjectIdentifier, at the given version.
func (sm *SourceMgr) ListPackages(id ProjectIdentifier, v Version) (PackageTree, error) {
	pmc, err := sm.getProjectManager(id)
	if err != nil {
		return PackageTree{}, err
	}

	return pmc.pm.ListPackages(id.ProjectRoot, v)
}

// ListVersions retrieves a list of the available versions for a given
// repository name.
//
// The list is not sorted; while it may be returned in the order that the
// underlying VCS reports version information, no guarantee is made. It is
// expected that the caller either not care about order, or sort the result
// themselves.
//
// This list is always retrieved from upstream on the first call. Subsequent
// calls will return a cached version of the first call's results. if upstream
// is not accessible (network outage, access issues, or the resource actually
// went away), an error will be returned.
func (sm *SourceMgr) ListVersions(id ProjectIdentifier) ([]Version, error) {
	pmc, err := sm.getProjectManager(id)
	if err != nil {
		// TODO(sdboyer) More-er proper-er errors
		return nil, err
	}

	return pmc.pm.ListVersions()
}

// RevisionPresentIn indicates whether the provided Revision is present in the given
// repository.
func (sm *SourceMgr) RevisionPresentIn(id ProjectIdentifier, r Revision) (bool, error) {
	pmc, err := sm.getProjectManager(id)
	if err != nil {
		// TODO(sdboyer) More-er proper-er errors
		return false, err
	}

	return pmc.pm.RevisionPresentIn(id.ProjectRoot, r)
}

// RepoExists checks if a repository exists, either upstream or in the cache,
// for the provided ProjectIdentifier.
func (sm *SourceMgr) RepoExists(id ProjectIdentifier) (bool, error) {
	pms, err := sm.getProjectManager(id)
	if err != nil {
		return false, err
	}

	return pms.pm.CheckExistence(existsInCache) || pms.pm.CheckExistence(existsUpstream), nil
}

// ExportProject writes out the tree of the provided ProjectIdentifier's
// ProjectRoot, at the provided version, to the provided directory.
func (sm *SourceMgr) ExportProject(id ProjectIdentifier, v Version, to string) error {
	pms, err := sm.getProjectManager(id)
	if err != nil {
		return err
	}

	return pms.pm.ExportVersionTo(v, to)
}

// getProjectManager gets the project manager for the given ProjectIdentifier.
//
// If no such manager yet exists, it attempts to create one.
func (sm *SourceMgr) getProjectManager(id ProjectIdentifier) (*pmState, error) {
	// TODO(sdboyer) finish this, it's not sufficient (?)
	n := id.netName()
	var rpath string

	// Early check to see if we already have a pm in the cache for this net name
	if pm, exists := sm.pms[n]; exists {
		return pm, nil
	}

	// Figure out the remote repo path
	rr, err := deduceRemoteRepo(n)
	if err != nil {
		// Not a valid import path, must reject
		// TODO(sdboyer) wrap error
		return nil, err
	}

	// Check the cache again, see if exact resulting clone url is in there
	if pm, exists := sm.pms[rr.CloneURL.String()]; exists {
		// Found it - re-register this PM at the original netname so that it
		// doesn't need to deduce next time
		// TODO(sdboyer) is this OK to do? are there consistency side effects?
		sm.pms[n] = pm
		return pm, nil
	}

	// No luck again. Now, walk through the scheme options the deducer returned,
	// checking if each is in the cache
	for _, scheme := range rr.Schemes {
		rr.CloneURL.Scheme = scheme
		// See if THIS scheme has a match, now
		if pm, exists := sm.pms[rr.CloneURL.String()]; exists {
			// Yep - again, re-register this PM at the original netname so that it
			// doesn't need to deduce next time
			// TODO(sdboyer) is this OK to do? are there consistency side effects?
			sm.pms[n] = pm
			return pm, nil
		}
	}

	// Definitively no match for anything in the cache, so we know we have to
	// create the entry. Next question is whether there's already a repo on disk
	// for any of the schemes, or if we need to create that, too.

	// TODO(sdboyer) this strategy kinda locks in the scheme to use over
	// multiple invocations in a way that maybe isn't the best.
	var r vcs.Repo
	for _, scheme := range rr.Schemes {
		rr.CloneURL.Scheme = scheme
		url := rr.CloneURL.String()
		sn := sanitizer.Replace(url)
		rpath = filepath.Join(sm.cachedir, "sources", sn)

		if fi, err := os.Stat(rpath); err == nil && fi.IsDir() {
			// This one exists, so set up here
			r, err = vcs.NewRepo(url, rpath)
			if err != nil {
				return nil, err
			}
			goto decided
		}
	}

	// Nothing on disk, either. Iterate through the schemes, trying each and
	// failing out only if none resulted in successfully setting up the local.
	for _, scheme := range rr.Schemes {
		rr.CloneURL.Scheme = scheme
		url := rr.CloneURL.String()
		sn := sanitizer.Replace(url)
		rpath = filepath.Join(sm.cachedir, "sources", sn)

		r, err = vcs.NewRepo(url, rpath)
		if err != nil {
			continue
		}

		// FIXME(sdboyer) cloning the repo here puts it on a blocking path. that
		// aspect of state management needs to be deferred into the
		// projectManager
		err = r.Get()
		if err != nil {
			continue
		}
		goto decided
	}

	// If we've gotten this far, we got some brokeass input.
	return nil, fmt.Errorf("Could not reach source repository for %s", n)

decided:
	// Ensure cache dir exists
	metadir := filepath.Join(sm.cachedir, "metadata", string(n))
	err = os.MkdirAll(metadir, 0777)
	if err != nil {
		// TODO(sdboyer) be better
		return nil, err
	}

	pms := &pmState{}
	cpath := filepath.Join(metadir, "cache.json")
	fi, err := os.Stat(cpath)
	var dc *sourceMetaCache
	if fi != nil {
		pms.cf, err = os.OpenFile(cpath, os.O_RDWR, 0777)
		if err != nil {
			// TODO(sdboyer) be better
			return nil, fmt.Errorf("Err on opening metadata cache file: %s", err)
		}

		err = json.NewDecoder(pms.cf).Decode(dc)
		if err != nil {
			// TODO(sdboyer) be better
			return nil, fmt.Errorf("Err on JSON decoding metadata cache file: %s", err)
		}
	} else {
		// TODO(sdboyer) commented this out for now, until we manage it correctly
		//pms.cf, err = os.Create(cpath)
		//if err != nil {
		//// TODO(sdboyer) be better
		//return nil, fmt.Errorf("Err on creating metadata cache file: %s", err)
		//}

		dc = newMetaCache()
	}

	pm := &projectManager{
		n:  n,
		an: sm.an,
		dc: dc,
		crepo: &repo{
			rpath: rpath,
			r:     r,
		},
	}

	pms.pm = pm
	sm.pms[n] = pms
	return pms, nil
}

func (sm *SourceMgr) whatsInAName(nn string) (*remoteRepo, error) {
	sm.rmut.RLock()
	tuple, exists := sm.rr[nn]
	sm.rmut.RUnlock()
	if exists {
		return tuple.rr, tuple.err
	}

	// Don't lock around the deduceRemoteRepo call, because that itself can be
	// slow. The tradeoff is that it's possible we might duplicate work if two
	// calls for the same id were to made simultaneously, but as those results
	// would be the same, clobbering is OK, and better than the alternative of
	// serializing all calls.
	rr, err := deduceRemoteRepo(nn)
	sm.rmut.Lock()
	sm.rr[nn] = struct {
		rr  *remoteRepo
		err error
	}{
		rr:  rr,
		err: err,
	}
	sm.rmut.Unlock()
	return rr, err
}
<commit_msg>Remove build.Context entirely from SourceMgr<commit_after>package gps

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"sync"

	"github.com/Masterminds/semver"
	"github.com/Masterminds/vcs"
)

// Used to compute a friendly filepath from a URL-shaped input
//
// TODO(sdboyer) this is awful. Right?
var sanitizer = strings.NewReplacer(":", "-", "/", "-", "+", "-")

// A SourceManager is responsible for retrieving, managing, and interrogating
// source repositories. Its primary purpose is to serve the needs of a Solver,
// but it is handy for other purposes, as well.
//
// gps's built-in SourceManager, SourceMgr, is intended to be generic and
// sufficient for any purpose. It provides some additional semantics around the
// methods defined here.
type SourceManager interface {
	// RepoExists checks if a repository exists, either upstream or in the
	// SourceManager's central repository cache.
	// TODO(sdboyer) rename to SourceExists
	RepoExists(ProjectIdentifier) (bool, error)

	// ListVersions retrieves a list of the available versions for a given
	// repository name.
	ListVersions(ProjectIdentifier) ([]Version, error)

	// RevisionPresentIn indicates whether the provided Version is present in
	// the given repository.
	RevisionPresentIn(ProjectIdentifier, Revision) (bool, error)

	// ListPackages parses the tree of the Go packages at or below root of the
	// provided ProjectIdentifier, at the provided version.
	ListPackages(ProjectIdentifier, Version) (PackageTree, error)

	// GetManifestAndLock returns manifest and lock information for the provided
	// root import path.
	//
	// gps currently requires that projects be rooted at their repository root,
	// necessitating that the ProjectIdentifier's ProjectRoot must also be a
	// repository root.
	GetManifestAndLock(ProjectIdentifier, Version) (Manifest, Lock, error)

	// ExportProject writes out the tree of the provided import path, at the
	// provided version, to the provided directory.
	ExportProject(ProjectIdentifier, Version, string) error

	// AnalyzerInfo reports the name and version of the logic used to service
	// GetManifestAndLock().
	AnalyzerInfo() (name string, version *semver.Version)
}

// A ProjectAnalyzer is responsible for analyzing a given path for Manifest and
// Lock information. Tools relying on gps must implement one.
type ProjectAnalyzer interface {
	// Perform analysis of the filesystem tree rooted at path, with the
	// root import path importRoot, to determine the project's constraints, as
	// indicated by a Manifest and Lock.
	DeriveManifestAndLock(path string, importRoot ProjectRoot) (Manifest, Lock, error)
	// Report the name and version of this ProjectAnalyzer.
	Info() (name string, version *semver.Version)
}

// SourceMgr is the default SourceManager for gps.
//
// There's no (planned) reason why it would need to be reimplemented by other
// tools; control via dependency injection is intended to be sufficient.
type SourceMgr struct {
	cachedir string
	pms      map[string]*pmState
	pmut     sync.RWMutex
	rr       map[string]struct {
		rr  *remoteRepo
		err error
	}
	rmut sync.RWMutex
	an   ProjectAnalyzer
}

var _ SourceManager = &SourceMgr{}

// Holds a projectManager, caches of the managed project's data, and information
// about the freshness of those caches
type pmState struct {
	pm   *projectManager
	cf   *os.File // handle for the cache file
	vcur bool     // indicates that we've called ListVersions()
}

// NewSourceManager produces an instance of gps's built-in SourceManager. It
// takes a cache directory (where local instances of upstream repositories are
// stored), a vendor directory for the project currently being worked on, and a
// force flag indicating whether to overwrite the global cache lock file (if
// present).
//
// The returned SourceManager aggressively caches information wherever possible.
// It is recommended that, if tools need to do preliminary, work involving
// upstream repository analysis prior to invoking a solve run, that they create
// this SourceManager as early as possible and use it to their ends. That way,
// the solver can benefit from any caches that may have already been warmed.
//
// gps's SourceManager is intended to be threadsafe (if it's not, please
// file a bug!). It should certainly be safe to reuse from one solving run to
// the next; however, the fact that it takes a basedir as an argument makes it
// much less useful for simultaneous use by separate solvers operating on
// different root projects. This architecture may change in the future.
func NewSourceManager(an ProjectAnalyzer, cachedir string, force bool) (*SourceMgr, error) {
	if an == nil {
		return nil, fmt.Errorf("a ProjectAnalyzer must be provided to the SourceManager")
	}

	err := os.MkdirAll(filepath.Join(cachedir, "sources"), 0777)
	if err != nil {
		return nil, err
	}

	glpath := filepath.Join(cachedir, "sm.lock")
	_, err = os.Stat(glpath)
	if err == nil && !force {
		return nil, fmt.Errorf("cache lock file %s exists - another process crashed or is still running?", glpath)
	}

	_, err = os.OpenFile(glpath, os.O_CREATE|os.O_RDONLY, 0700) // is 0700 sane for this purpose?
	if err != nil {
		return nil, fmt.Errorf("failed to create global cache lock file at %s with err %s", glpath, err)
	}

	return &SourceMgr{
		cachedir: cachedir,
		pms:      make(map[string]*pmState),
		rr: make(map[string]struct {
			rr  *remoteRepo
			err error
		}),
		an: an,
	}, nil
}

// Release lets go of any locks held by the SourceManager.
func (sm *SourceMgr) Release() {
	os.Remove(filepath.Join(sm.cachedir, "sm.lock"))
}

// AnalyzerInfo reports the name and version of the injected ProjectAnalyzer.
func (sm *SourceMgr) AnalyzerInfo() (name string, version *semver.Version) {
	return sm.an.Info()
}

// GetManifestAndLock returns manifest and lock information for the provided
// import path. gps currently requires that projects be rooted at their
// repository root, necessitating that the ProjectIdentifier's ProjectRoot must
// also be a repository root.
//
// The work of producing the manifest and lock is delegated to the injected
// ProjectAnalyzer's DeriveManifestAndLock() method.
func (sm *SourceMgr) GetManifestAndLock(id ProjectIdentifier, v Version) (Manifest, Lock, error) {
	pmc, err := sm.getProjectManager(id)
	if err != nil {
		return nil, nil, err
	}

	return pmc.pm.GetManifestAndLock(id.ProjectRoot, v)
}

// ListPackages parses the tree of the Go packages at and below the ProjectRoot
// of the given ProjectIdentifier, at the given version.
func (sm *SourceMgr) ListPackages(id ProjectIdentifier, v Version) (PackageTree, error) {
	pmc, err := sm.getProjectManager(id)
	if err != nil {
		return PackageTree{}, err
	}

	return pmc.pm.ListPackages(id.ProjectRoot, v)
}

// ListVersions retrieves a list of the available versions for a given
// repository name.
//
// The list is not sorted; while it may be returned in the order that the
// underlying VCS reports version information, no guarantee is made. It is
// expected that the caller either not care about order, or sort the result
// themselves.
//
// This list is always retrieved from upstream on the first call. Subsequent
// calls will return a cached version of the first call's results. if upstream
// is not accessible (network outage, access issues, or the resource actually
// went away), an error will be returned.
func (sm *SourceMgr) ListVersions(id ProjectIdentifier) ([]Version, error) {
	pmc, err := sm.getProjectManager(id)
	if err != nil {
		// TODO(sdboyer) More-er proper-er errors
		return nil, err
	}

	return pmc.pm.ListVersions()
}

// RevisionPresentIn indicates whether the provided Revision is present in the given
// repository.
func (sm *SourceMgr) RevisionPresentIn(id ProjectIdentifier, r Revision) (bool, error) {
	pmc, err := sm.getProjectManager(id)
	if err != nil {
		// TODO(sdboyer) More-er proper-er errors
		return false, err
	}

	return pmc.pm.RevisionPresentIn(id.ProjectRoot, r)
}

// RepoExists checks if a repository exists, either upstream or in the cache,
// for the provided ProjectIdentifier.
func (sm *SourceMgr) RepoExists(id ProjectIdentifier) (bool, error) {
	pms, err := sm.getProjectManager(id)
	if err != nil {
		return false, err
	}

	return pms.pm.CheckExistence(existsInCache) || pms.pm.CheckExistence(existsUpstream), nil
}

// ExportProject writes out the tree of the provided ProjectIdentifier's
// ProjectRoot, at the provided version, to the provided directory.
func (sm *SourceMgr) ExportProject(id ProjectIdentifier, v Version, to string) error {
	pms, err := sm.getProjectManager(id)
	if err != nil {
		return err
	}

	return pms.pm.ExportVersionTo(v, to)
}

// getProjectManager gets the project manager for the given ProjectIdentifier.
//
// If no such manager yet exists, it attempts to create one.
func (sm *SourceMgr) getProjectManager(id ProjectIdentifier) (*pmState, error) {
	// TODO(sdboyer) finish this, it's not sufficient (?)
	n := id.netName()
	var rpath string

	// Early check to see if we already have a pm in the cache for this net name
	if pm, exists := sm.pms[n]; exists {
		return pm, nil
	}

	// Figure out the remote repo path
	rr, err := deduceRemoteRepo(n)
	if err != nil {
		// Not a valid import path, must reject
		// TODO(sdboyer) wrap error
		return nil, err
	}

	// Check the cache again, see if exact resulting clone url is in there
	if pm, exists := sm.pms[rr.CloneURL.String()]; exists {
		// Found it - re-register this PM at the original netname so that it
		// doesn't need to deduce next time
		// TODO(sdboyer) is this OK to do? are there consistency side effects?
		sm.pms[n] = pm
		return pm, nil
	}

	// No luck again. Now, walk through the scheme options the deducer returned,
	// checking if each is in the cache
	for _, scheme := range rr.Schemes {
		rr.CloneURL.Scheme = scheme
		// See if THIS scheme has a match, now
		if pm, exists := sm.pms[rr.CloneURL.String()]; exists {
			// Yep - again, re-register this PM at the original netname so that it
			// doesn't need to deduce next time
			// TODO(sdboyer) is this OK to do? are there consistency side effects?
			sm.pms[n] = pm
			return pm, nil
		}
	}

	// Definitively no match for anything in the cache, so we know we have to
	// create the entry. Next question is whether there's already a repo on disk
	// for any of the schemes, or if we need to create that, too.

	// TODO(sdboyer) this strategy kinda locks in the scheme to use over
	// multiple invocations in a way that maybe isn't the best.
	var r vcs.Repo
	for _, scheme := range rr.Schemes {
		rr.CloneURL.Scheme = scheme
		url := rr.CloneURL.String()
		sn := sanitizer.Replace(url)
		rpath = filepath.Join(sm.cachedir, "sources", sn)

		if fi, err := os.Stat(rpath); err == nil && fi.IsDir() {
			// This one exists, so set up here
			r, err = vcs.NewRepo(url, rpath)
			if err != nil {
				return nil, err
			}
			goto decided
		}
	}

	// Nothing on disk, either. Iterate through the schemes, trying each and
	// failing out only if none resulted in successfully setting up the local.
	for _, scheme := range rr.Schemes {
		rr.CloneURL.Scheme = scheme
		url := rr.CloneURL.String()
		sn := sanitizer.Replace(url)
		rpath = filepath.Join(sm.cachedir, "sources", sn)

		r, err = vcs.NewRepo(url, rpath)
		if err != nil {
			continue
		}

		// FIXME(sdboyer) cloning the repo here puts it on a blocking path. that
		// aspect of state management needs to be deferred into the
		// projectManager
		err = r.Get()
		if err != nil {
			continue
		}
		goto decided
	}

	// If we've gotten this far, we got some brokeass input.
	return nil, fmt.Errorf("Could not reach source repository for %s", n)

decided:
	// Ensure cache dir exists
	metadir := filepath.Join(sm.cachedir, "metadata", string(n))
	err = os.MkdirAll(metadir, 0777)
	if err != nil {
		// TODO(sdboyer) be better
		return nil, err
	}

	pms := &pmState{}
	cpath := filepath.Join(metadir, "cache.json")
	fi, err := os.Stat(cpath)
	var dc *sourceMetaCache
	if fi != nil {
		pms.cf, err = os.OpenFile(cpath, os.O_RDWR, 0777)
		if err != nil {
			// TODO(sdboyer) be better
			return nil, fmt.Errorf("Err on opening metadata cache file: %s", err)
		}

		err = json.NewDecoder(pms.cf).Decode(dc)
		if err != nil {
			// TODO(sdboyer) be better
			return nil, fmt.Errorf("Err on JSON decoding metadata cache file: %s", err)
		}
	} else {
		// TODO(sdboyer) commented this out for now, until we manage it correctly
		//pms.cf, err = os.Create(cpath)
		//if err != nil {
		//// TODO(sdboyer) be better
		//return nil, fmt.Errorf("Err on creating metadata cache file: %s", err)
		//}

		dc = newMetaCache()
	}

	pm := &projectManager{
		n:  n,
		an: sm.an,
		dc: dc,
		crepo: &repo{
			rpath: rpath,
			r:     r,
		},
	}

	pms.pm = pm
	sm.pms[n] = pms
	return pms, nil
}

func (sm *SourceMgr) whatsInAName(nn string) (*remoteRepo, error) {
	sm.rmut.RLock()
	tuple, exists := sm.rr[nn]
	sm.rmut.RUnlock()
	if exists {
		return tuple.rr, tuple.err
	}

	// Don't lock around the deduceRemoteRepo call, because that itself can be
	// slow. The tradeoff is that it's possible we might duplicate work if two
	// calls for the same id were to made simultaneously, but as those results
	// would be the same, clobbering is OK, and better than the alternative of
	// serializing all calls.
	rr, err := deduceRemoteRepo(nn)
	sm.rmut.Lock()
	sm.rr[nn] = struct {
		rr  *remoteRepo
		err error
	}{
		rr:  rr,
		err: err,
	}
	sm.rmut.Unlock()
	return rr, err
}
<|endoftext|>


<commit_before>package mi

import (
	"github.com/stretchr/testify/assert"
	"io/ioutil"
	"testing"
)

func TestUnmarshalWithInvalidXML(t *testing.T) {
	data := `invalid`
	v, err := Unmarshal([]byte(data))
	assert.NotNil(t, err)
	assert.Nil(t, v)
}

func TestUnmarshalCancelRequest(t *testing.T) {
	data, err := ioutil.ReadFile("examples/cancel_request.xml")
	if assert.Nil(t, err) {
		v, err := Unmarshal(data)
		if assert.Nil(t, err) {
			assert.Equal(t, "1.0", v.Version)
			assert.Equal(t, 10, v.Ttl)
			if assert.Len(t, v.Cancel.RequestIds, 2) {
				assert.Equal(t, "REQ0011212121212", v.Cancel.RequestIds[0].Text)
				assert.Equal(t, "REQ0011212121213", v.Cancel.RequestIds[1].Text)
			}
		}
	}
}

func TestUnmarshalCancelRequestWithNodes(t *testing.T) {
	data, err := ioutil.ReadFile("examples/cancel_request_with_nodes.xml")
	if assert.Nil(t, err) {
		v, err := Unmarshal(data)
		if assert.Nil(t, err) {
			assert.Equal(t, "1.0", v.Version)
			assert.Equal(t, 10, v.Ttl)
			if assert.Len(t, v.Cancel.RequestIds, 1) {
				assert.Equal(t, "REQ0011212121212", v.Cancel.RequestIds[0].Text)
			}
			assert.Equal(t, "URL", v.Cancel.NodeList.Type)
			if assert.Len(t, v.Cancel.NodeList.Nodes, 2) {
				assert.Equal(t, "http://192.168.0.1/", v.Cancel.NodeList.Nodes[0])
				assert.Equal(t, "http://192.168.0.2/", v.Cancel.NodeList.Nodes[1])
			}
		}
	}
}

func TestUnmarshalErrorResponse(t *testing.T) {
	data, err := ioutil.ReadFile("examples/error_response.xml")
	if assert.Nil(t, err) {
		v, err := Unmarshal(data)
		if assert.Nil(t, err) {
			assert.Equal(t, "1.0", v.Version)
			assert.Equal(t, 0, v.Ttl)
			if assert.Len(t, v.Response.Results, 1) {
				assert.Equal(t, "404", v.Response.Results[0].Return.ReturnCode)
			}
		}
	}
}

func TestUnmarshalErrorResponseWithDescription(t *testing.T) {
	data, err := ioutil.ReadFile("examples/error_response_with_description.xml")
	if assert.Nil(t, err) {
		v, err := Unmarshal(data)
		if assert.Nil(t, err) {
			if assert.Len(t, v.Response.Results, 1) {
				assert.Equal(t, "Not Found", v.Response.Results[0].Return.Description)
			}
		}
	}
}

func TestUnmarshalMultiplePayloadResponse(t *testing.T) {
	data, err := ioutil.ReadFile("examples/multiple_payload_response.xml")
	if assert.Nil(t, err) {
		v, err := Unmarshal(data)
		if assert.Nil(t, err) {
			assert.Equal(t, "1.0", v.Version)
			assert.Equal(t, 10, v.Ttl)
			if assert.Len(t, v.Response.Results, 3) {
				assert.Equal(t, "obix", v.Response.Results[0].MsgFormat)
				assert.Equal(t, "200", v.Response.Results[0].Return.ReturnCode)
				assert.Equal(t, "REQ0011212121212", v.Response.Results[0].RequestId.Text)
				assert.Equal(t, `
                <obj href="http://myhome/thermostat" >
                    <real name="spaceTemp" unit="obix:units/fahrenheit" val="67.2"/>
                    <real name="setpoint" unit="obix:units/fahrenheit" val="72.0"/>
                    <bool name="furnaceOn" val="true"/>
                </obj>
            `, v.Response.Results[0].Message.Data)

				assert.Equal(t, "CSV", v.Response.Results[1].MsgFormat)
				assert.Equal(t, "200", v.Response.Results[1].Return.ReturnCode)
				assert.Equal(t, "REQ232323", v.Response.Results[1].RequestId.Text)
				assert.Equal(t, `11,22,33
                44,55,66`, v.Response.Results[1].Message.Data)

				assert.Equal(t, "QLMdf", v.Response.Results[2].MsgFormat)
				assert.Equal(t, "200", v.Response.Results[2].Return.ReturnCode)
				assert.Equal(t, "REQ654534", v.Response.Results[2].RequestId.Text)
				assert.Equal(t, `
                <Objects>
                    <Object>
                        <id>SmartFridge22334411</id>
                        <InfoItem name="PowerConsumption">
                            <value>43</value>
                        </InfoItem>
                    </Object>
                </Objects>
            `, v.Response.Results[2].Message.Data)
			}
		}
	}
}

func TestUnmarshalPublishing(t *testing.T) {
	data, err := ioutil.ReadFile("examples/publishing.xml")
	if assert.Nil(t, err) {
		v, err := Unmarshal(data)
		if assert.Nil(t, err) {
			assert.Equal(t, "1.0", v.Version)
			assert.Equal(t, -1, v.Ttl)
			assert.Equal(t, "QLMdf", v.Write.MsgFormat)
			assert.Equal(t, `
            <Objects>
                <Object>
                    <id>SmartFridge22334411</id>
                    <InfoItem name="FridgeTemperatureSetpoint">
                        <value>3.5</value>
                    </InfoItem>
                </Object>
            </Objects>
        `, v.Write.Message.Data)
		}
	}
}

func TestUnmarshalReadRequest(t *testing.T) {
	data, err := ioutil.ReadFile("examples/read_request.xml")
	if assert.Nil(t, err) {
		v, err := Unmarshal(data)
		if assert.Nil(t, err) {
			assert.Equal(t, "1.0", v.Version)
			assert.Equal(t, 10, v.Ttl)
			assert.Equal(t, "QLM_mf.xsd", v.Read.MsgFormat)
			assert.Equal(t, 3.5, v.Read.Interval)
			assert.Equal(t, 10, v.Read.Oldest)
			assert.Equal(t, "2014-01-01T00:00", v.Read.Begin)
			assert.Equal(t, "2014-02-01T00:00", v.Read.End)
			assert.Equal(t, 15, v.Read.Newest)
			assert.Equal(t, `
            <Objects>
                <Object>
                    <id>SmartFridge22334411</id>
                    <InfoItem name="PowerConsumption"></InfoItem>
                </Object>
            </Objects>
        `, v.Read.Message.Data)
		}
	}
}

func TestUnmarshalReadRequestWithCallback(t *testing.T) {
	data, err := ioutil.ReadFile("examples/read_request_with_callback.xml")
	if assert.Nil(t, err) {
		v, err := Unmarshal(data)
		if assert.Nil(t, err) {
			assert.Equal(t, "http://192.168.0.1/", v.Read.Callback)
		}
	}
}

func TestUnmarshalReadRequestWithNodes(t *testing.T) {
	data, err := ioutil.ReadFile("examples/read_request_with_nodes.xml")
	if assert.Nil(t, err) {
		v, err := Unmarshal(data)
		if assert.Nil(t, err) {
			assert.Equal(t, "URL", v.Read.NodeList.Type)
			if assert.Len(t, v.Read.NodeList.Nodes, 2) {
				assert.Equal(t, "http://192.168.0.1/", v.Read.NodeList.Nodes[0])
				assert.Equal(t, "http://192.168.0.2/", v.Read.NodeList.Nodes[1])
			}
		}
	}
}

func TestUnmarshalReadResponseMetadata(t *testing.T) {
	data, err := ioutil.ReadFile("examples/read_response_metadata.xml")
	if assert.Nil(t, err) {
		v, err := Unmarshal(data)
		if assert.Nil(t, err) {
			assert.Equal(t, "1.0", v.Version)
			assert.Equal(t, 10, v.Ttl)
			if assert.Len(t, v.Response.Results, 1) {
				assert.Equal(t, "QLMdf", v.Response.Results[0].MsgFormat)
				assert.Equal(t, "200", v.Response.Results[0].Return.ReturnCode)
				assert.Equal(t, "REQ654534", v.Response.Results[0].RequestId.Text)
				assert.Equal(t, `
                <Objects>
                    <Object>
                        <id>SmartFridge22334411</id>
                        <InfoItem name="PowerConsumption">
                            <MetaData>
                                <InfoItem name="format">
                                    <value type="xs:string">xs:double</value>
                                </InfoItem>
                                <InfoItem name="latency">
                                    <value type="xs:int">5</value>
                                </InfoItem>
                                <InfoItem name="readable">
                                    <value type="xs:boolean">true</value>
                                </InfoItem>
                                <InfoItem name="writable">
                                    <value type="xs:boolean">false</value>
                                </InfoItem>
                                <InfoItem name="unit">
                                    <value type="xs:string">Watts</value>
                                </InfoItem>
                                <InfoItem name="accuracy">
                                    <value type="xs:double">1</value>
                                </InfoItem>
                            </MetaData>
                        </InfoItem>
                    </Object>
                </Objects>
            `, v.Response.Results[0].Message.Data)
			}
		}
	}
}

func TestUnmarshalReadResponseWithRequestIdFormat(t *testing.T) {
	data, err := ioutil.ReadFile("examples/read_response_with_request_id_format.xml")
	if assert.Nil(t, err) {
		v, err := Unmarshal(data)
		if assert.Nil(t, err) {
			if assert.Len(t, v.Response.Results, 1) {
				assert.Equal(t, "REQ", v.Response.Results[0].RequestId.Format)
			}
		}
	}
}
func TestUnmarshalResponseWithNodes(t *testing.T) {
	data, err := ioutil.ReadFile("examples/response_with_nodes.xml")
	if assert.Nil(t, err) {
		v, err := Unmarshal(data)
		if assert.Nil(t, err) && assert.Len(t, v.Response.Results, 1) {
			assert.Equal(t, "URL", v.Response.Results[0].NodeList.Type)
			if assert.Len(t, v.Response.Results[0].NodeList.Nodes, 2) {
				assert.Equal(t, "http://192.168.0.1/", v.Response.Results[0].NodeList.Nodes[0])
				assert.Equal(t, "http://192.168.0.2/", v.Response.Results[0].NodeList.Nodes[1])
			}
		}
	}
}

func TestUnmarshalTypicalMinimalResponse(t *testing.T) {
	data, err := ioutil.ReadFile("examples/typical_minimal_response.xml")
	if assert.Nil(t, err) {
		v, err := Unmarshal(data)
		if assert.Nil(t, err) {
			assert.Equal(t, "0.2", v.Version)
			assert.Equal(t, 0, v.Ttl)
			if assert.Len(t, v.Response.Results, 1) {
				assert.Equal(t, "200", v.Response.Results[0].Return.ReturnCode)
			}
		}
	}
}

func TestUnmarshalWriteRequest(t *testing.T) {
	data, err := ioutil.ReadFile("examples/write_request.xml")
	if assert.Nil(t, err) {
		v, err := Unmarshal(data)
		if assert.Nil(t, err) {
			assert.Equal(t, "1.0", v.Version)
			assert.Equal(t, -1, v.Ttl)
			assert.Equal(t, "QLMdf", v.Write.MsgFormat)
			assert.Equal(t, "device", v.Write.TargetType)
			assert.Equal(t, `
            <Objects>
                <Object>
                    <id>SmartFridge22334411</id>
                    <InfoItem name="FridgeTemperatureSetpoint">
                        <value>3.5</value>
                    </InfoItem>
                    <InfoItem name="FreezerTemperatureSetpoint">
                        <value>-20.0</value>
                    </InfoItem>
                </Object>
            </Objects>
        `, v.Write.Message.Data)
		}
	}
}
<commit_msg>Add test to check that nil pointers are valid for data that should not be unmarshaled<commit_after>package mi

import (
	"github.com/stretchr/testify/assert"
	"io/ioutil"
	"testing"
)

func TestUnmarshalWithInvalidXML(t *testing.T) {
	data := `invalid`
	v, err := Unmarshal([]byte(data))
	assert.NotNil(t, err)
	assert.Nil(t, v)
}

func TestUnmarshalCancelRequest(t *testing.T) {
	data, err := ioutil.ReadFile("examples/cancel_request.xml")
	if assert.Nil(t, err) {
		v, err := Unmarshal(data)
		if assert.Nil(t, err) {
			assert.Equal(t, "1.0", v.Version)
			assert.Equal(t, 10, v.Ttl)
			if assert.Len(t, v.Cancel.RequestIds, 2) {
				assert.Equal(t, "REQ0011212121212", v.Cancel.RequestIds[0].Text)
				assert.Equal(t, "REQ0011212121213", v.Cancel.RequestIds[1].Text)
			}
		}
	}
}

func TestUnmarshalCancelRequestWithNodes(t *testing.T) {
	data, err := ioutil.ReadFile("examples/cancel_request_with_nodes.xml")
	if assert.Nil(t, err) {
		v, err := Unmarshal(data)
		if assert.Nil(t, err) {
			assert.Equal(t, "1.0", v.Version)
			assert.Equal(t, 10, v.Ttl)
			if assert.Len(t, v.Cancel.RequestIds, 1) {
				assert.Equal(t, "REQ0011212121212", v.Cancel.RequestIds[0].Text)
			}
			assert.Equal(t, "URL", v.Cancel.NodeList.Type)
			if assert.Len(t, v.Cancel.NodeList.Nodes, 2) {
				assert.Equal(t, "http://192.168.0.1/", v.Cancel.NodeList.Nodes[0])
				assert.Equal(t, "http://192.168.0.2/", v.Cancel.NodeList.Nodes[1])
			}
		}
	}
}

func TestUnmarshalErrorResponse(t *testing.T) {
	data, err := ioutil.ReadFile("examples/error_response.xml")
	if assert.Nil(t, err) {
		v, err := Unmarshal(data)
		if assert.Nil(t, err) {
			assert.Equal(t, "1.0", v.Version)
			assert.Equal(t, 0, v.Ttl)
			if assert.Len(t, v.Response.Results, 1) {
				assert.Equal(t, "404", v.Response.Results[0].Return.ReturnCode)
			}
		}
	}
}

func TestUnmarshalErrorResponseWithDescription(t *testing.T) {
	data, err := ioutil.ReadFile("examples/error_response_with_description.xml")
	if assert.Nil(t, err) {
		v, err := Unmarshal(data)
		if assert.Nil(t, err) {
			if assert.Len(t, v.Response.Results, 1) {
				assert.Equal(t, "Not Found", v.Response.Results[0].Return.Description)
			}
		}
	}
}

func TestUnmarshalMultiplePayloadResponse(t *testing.T) {
	data, err := ioutil.ReadFile("examples/multiple_payload_response.xml")
	if assert.Nil(t, err) {
		v, err := Unmarshal(data)
		if assert.Nil(t, err) {
			assert.Equal(t, "1.0", v.Version)
			assert.Equal(t, 10, v.Ttl)
			if assert.Len(t, v.Response.Results, 3) {
				assert.Equal(t, "obix", v.Response.Results[0].MsgFormat)
				assert.Equal(t, "200", v.Response.Results[0].Return.ReturnCode)
				assert.Equal(t, "REQ0011212121212", v.Response.Results[0].RequestId.Text)
				assert.Equal(t, `
                <obj href="http://myhome/thermostat" >
                    <real name="spaceTemp" unit="obix:units/fahrenheit" val="67.2"/>
                    <real name="setpoint" unit="obix:units/fahrenheit" val="72.0"/>
                    <bool name="furnaceOn" val="true"/>
                </obj>
            `, v.Response.Results[0].Message.Data)

				assert.Equal(t, "CSV", v.Response.Results[1].MsgFormat)
				assert.Equal(t, "200", v.Response.Results[1].Return.ReturnCode)
				assert.Equal(t, "REQ232323", v.Response.Results[1].RequestId.Text)
				assert.Equal(t, `11,22,33
                44,55,66`, v.Response.Results[1].Message.Data)

				assert.Equal(t, "QLMdf", v.Response.Results[2].MsgFormat)
				assert.Equal(t, "200", v.Response.Results[2].Return.ReturnCode)
				assert.Equal(t, "REQ654534", v.Response.Results[2].RequestId.Text)
				assert.Equal(t, `
                <Objects>
                    <Object>
                        <id>SmartFridge22334411</id>
                        <InfoItem name="PowerConsumption">
                            <value>43</value>
                        </InfoItem>
                    </Object>
                </Objects>
            `, v.Response.Results[2].Message.Data)
			}
		}
	}
}

func TestUnmarshalPublishing(t *testing.T) {
	data, err := ioutil.ReadFile("examples/publishing.xml")
	if assert.Nil(t, err) {
		v, err := Unmarshal(data)
		if assert.Nil(t, err) {
			assert.Equal(t, "1.0", v.Version)
			assert.Equal(t, -1, v.Ttl)
			assert.Equal(t, "QLMdf", v.Write.MsgFormat)
			assert.Equal(t, `
            <Objects>
                <Object>
                    <id>SmartFridge22334411</id>
                    <InfoItem name="FridgeTemperatureSetpoint">
                        <value>3.5</value>
                    </InfoItem>
                </Object>
            </Objects>
        `, v.Write.Message.Data)
		}
	}
}

func TestUnmarshalReadRequest(t *testing.T) {
	data, err := ioutil.ReadFile("examples/read_request.xml")
	if assert.Nil(t, err) {
		v, err := Unmarshal(data)
		if assert.Nil(t, err) {
			assert.Equal(t, "1.0", v.Version)
			assert.Equal(t, 10, v.Ttl)
			assert.Equal(t, "QLM_mf.xsd", v.Read.MsgFormat)
			assert.Equal(t, 3.5, v.Read.Interval)
			assert.Equal(t, 10, v.Read.Oldest)
			assert.Equal(t, "2014-01-01T00:00", v.Read.Begin)
			assert.Equal(t, "2014-02-01T00:00", v.Read.End)
			assert.Equal(t, 15, v.Read.Newest)
			assert.Equal(t, `
            <Objects>
                <Object>
                    <id>SmartFridge22334411</id>
                    <InfoItem name="PowerConsumption"></InfoItem>
                </Object>
            </Objects>
        `, v.Read.Message.Data)
		}
	}
}

func TestUnmarshalReadRequestWithCallback(t *testing.T) {
	data, err := ioutil.ReadFile("examples/read_request_with_callback.xml")
	if assert.Nil(t, err) {
		v, err := Unmarshal(data)
		if assert.Nil(t, err) {
			assert.Equal(t, "http://192.168.0.1/", v.Read.Callback)
		}
	}
}

func TestUnmarshalReadRequestWithNodes(t *testing.T) {
	data, err := ioutil.ReadFile("examples/read_request_with_nodes.xml")
	if assert.Nil(t, err) {
		v, err := Unmarshal(data)
		if assert.Nil(t, err) {
			assert.Equal(t, "URL", v.Read.NodeList.Type)
			if assert.Len(t, v.Read.NodeList.Nodes, 2) {
				assert.Equal(t, "http://192.168.0.1/", v.Read.NodeList.Nodes[0])
				assert.Equal(t, "http://192.168.0.2/", v.Read.NodeList.Nodes[1])
			}
		}
	}
}

func TestUnmarshalReadResponseMetadata(t *testing.T) {
	data, err := ioutil.ReadFile("examples/read_response_metadata.xml")
	if assert.Nil(t, err) {
		v, err := Unmarshal(data)
		if assert.Nil(t, err) {
			assert.Equal(t, "1.0", v.Version)
			assert.Equal(t, 10, v.Ttl)
			if assert.Len(t, v.Response.Results, 1) {
				assert.Equal(t, "QLMdf", v.Response.Results[0].MsgFormat)
				assert.Equal(t, "200", v.Response.Results[0].Return.ReturnCode)
				assert.Equal(t, "REQ654534", v.Response.Results[0].RequestId.Text)
				assert.Equal(t, `
                <Objects>
                    <Object>
                        <id>SmartFridge22334411</id>
                        <InfoItem name="PowerConsumption">
                            <MetaData>
                                <InfoItem name="format">
                                    <value type="xs:string">xs:double</value>
                                </InfoItem>
                                <InfoItem name="latency">
                                    <value type="xs:int">5</value>
                                </InfoItem>
                                <InfoItem name="readable">
                                    <value type="xs:boolean">true</value>
                                </InfoItem>
                                <InfoItem name="writable">
                                    <value type="xs:boolean">false</value>
                                </InfoItem>
                                <InfoItem name="unit">
                                    <value type="xs:string">Watts</value>
                                </InfoItem>
                                <InfoItem name="accuracy">
                                    <value type="xs:double">1</value>
                                </InfoItem>
                            </MetaData>
                        </InfoItem>
                    </Object>
                </Objects>
            `, v.Response.Results[0].Message.Data)
			}
		}
	}
}

func TestUnmarshalReadResponseWithRequestIdFormat(t *testing.T) {
	data, err := ioutil.ReadFile("examples/read_response_with_request_id_format.xml")
	if assert.Nil(t, err) {
		v, err := Unmarshal(data)
		if assert.Nil(t, err) {
			if assert.Len(t, v.Response.Results, 1) {
				assert.Equal(t, "REQ", v.Response.Results[0].RequestId.Format)
			}
		}
	}
}
func TestUnmarshalResponseWithNodes(t *testing.T) {
	data, err := ioutil.ReadFile("examples/response_with_nodes.xml")
	if assert.Nil(t, err) {
		v, err := Unmarshal(data)
		if assert.Nil(t, err) && assert.Len(t, v.Response.Results, 1) {
			assert.Equal(t, "URL", v.Response.Results[0].NodeList.Type)
			if assert.Len(t, v.Response.Results[0].NodeList.Nodes, 2) {
				assert.Equal(t, "http://192.168.0.1/", v.Response.Results[0].NodeList.Nodes[0])
				assert.Equal(t, "http://192.168.0.2/", v.Response.Results[0].NodeList.Nodes[1])
			}
		}
	}
}

func TestUnmarshalTypicalMinimalResponse(t *testing.T) {
	data, err := ioutil.ReadFile("examples/typical_minimal_response.xml")
	if assert.Nil(t, err) {
		v, err := Unmarshal(data)
		if assert.Nil(t, err) {
			assert.Equal(t, "0.2", v.Version)
			assert.Equal(t, 0, v.Ttl)
			if assert.Len(t, v.Response.Results, 1) {
				assert.Equal(t, "200", v.Response.Results[0].Return.ReturnCode)
			}
		}
	}
}

func TestUnmarshalWriteRequest(t *testing.T) {
	data, err := ioutil.ReadFile("examples/write_request.xml")
	if assert.Nil(t, err) {
		v, err := Unmarshal(data)
		if assert.Nil(t, err) {
			assert.Equal(t, "1.0", v.Version)
			assert.Equal(t, -1, v.Ttl)
			assert.Equal(t, "QLMdf", v.Write.MsgFormat)
			assert.Equal(t, "device", v.Write.TargetType)
			assert.Equal(t, `
            <Objects>
                <Object>
                    <id>SmartFridge22334411</id>
                    <InfoItem name="FridgeTemperatureSetpoint">
                        <value>3.5</value>
                    </InfoItem>
                    <InfoItem name="FreezerTemperatureSetpoint">
                        <value>-20.0</value>
                    </InfoItem>
                </Object>
            </Objects>
        `, v.Write.Message.Data)
		}
	}
}

func TestUnmarshalReadRequestPointers(t *testing.T) {
	data, err := ioutil.ReadFile("examples/read_request.xml")
	if assert.Nil(t, err) {
		v, err := Unmarshal(data)
		if assert.Nil(t, err) {
			assert.Nil(t, v.Write)
			assert.NotNil(t, v.Read)
		}
	}
}
<|endoftext|>


<commit_before>package middleware

import (
	"fmt"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strings"

	"github.com/admpub/log"
	"github.com/webx-top/echo"
	"github.com/webx-top/echo/engine"
)

type (
	StaticOptions struct {
		// Skipper defines a function to skip middleware.
		Skipper echo.Skipper `json:"-"`

		Path   string `json:"path"` //UrlPath
		Root   string `json:"root"`
		Index  string `json:"index"`
		Browse bool   `json:"browse"`
	}
)

func Static(options ...*StaticOptions) echo.MiddlewareFunc {
	// Default options
	opts := new(StaticOptions)
	if len(options) > 0 {
		opts = options[0]
	}
	hasIndex := len(opts.Index) > 0
	if opts.Skipper == nil {
		opts.Skipper = echo.DefaultSkipper
	}
	check := hasIndex || opts.Browse
	opts.Root, _ = filepath.Abs(opts.Root)
	length := len(opts.Path)
	if length > 0 && opts.Path[0] != '/' {
		opts.Path = `/` + opts.Path
		length++
	}

	log.GetLogger("echo").Debugf("Static: %v\t-> %v", opts.Path, opts.Root)

	return func(next echo.Handler) echo.Handler {
		return echo.HandlerFunc(func(c echo.Context) error {
			if opts.Skipper(c) {
				return next.Handle(c)
			}
			file := c.Request().URL().Path()
			if len(file) < length || file[0:length] != opts.Path {
				return next.Handle(c)
			}
			file = file[length:]
			file = path.Clean(file)
			absFile := filepath.Join(opts.Root, file)
			if !strings.HasPrefix(absFile, opts.Root) {
				return next.Handle(c)
			}
			w := c.Response()
			if !check {
				w.ServeFile(absFile)
				return nil
			}
			fi, err := os.Stat(absFile)
			if err != nil {
				return echo.ErrNotFound
			}
			if fi.IsDir() {
				// Index file
				indexFile := filepath.Join(absFile, opts.Index)
				fi, err = os.Stat(indexFile)
				if err != nil || fi.IsDir() {
					if opts.Browse {
						return listDir(absFile, file, w)
					}
					return echo.ErrNotFound
				}
				absFile = indexFile
			}
			w.ServeFile(absFile)
			return nil
		})
	}
}

func listDir(absFile string, file string, w engine.Response) error {
	fs := http.Dir(filepath.Dir(absFile))
	d, err := fs.Open(filepath.Base(absFile))
	if err != nil {
		return echo.ErrNotFound
	}
	defer d.Close()
	dirs, err := d.Readdir(-1)
	if err != nil {
		return echo.ErrNotFound
	}

	// Create a directory index
	w.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8)
	if _, err = fmt.Fprintf(w, `<!doctype html>

        <meta charset="UTF-8">
        <title>`+file+`</title>
        <meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible" />
        <meta content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport" />
        <link href="/favicon.ico" rel="shortcut icon">

    `); err != nil {
		return err
	}
	if _, err = fmt.Fprintf(w, "
\n"); err != nil {
		return err
	}
	for _, d := range dirs {
		name := d.Name()
		color := "#212121"
		if d.IsDir() {
			color = "#e91e63"
			name += "/"
		}
		if _, err = fmt.Fprintf(w, "
%s
\n", name, color, name); err != nil {
			return err
		}
	}
	if _, err = fmt.Fprintf(w, "
\n"); err != nil {
		return err
	}
	_, err = fmt.Fprintf(w, "\n")
	return err
}
<commit_msg>update<commit_after>package middleware

import (
	"fmt"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strings"

	"github.com/admpub/log"
	"github.com/webx-top/echo"
	"github.com/webx-top/echo/engine"
)

type (
	StaticOptions struct {
		// Skipper defines a function to skip middleware.
		Skipper echo.Skipper `json:"-"`

		Path   string `json:"path"` //UrlPath
		Root   string `json:"root"`
		Index  string `json:"index"`
		Browse bool   `json:"browse"`
	}
)

func Static(options ...*StaticOptions) echo.MiddlewareFunc {
	// Default options
	opts := new(StaticOptions)
	if len(options) > 0 {
		opts = options[0]
	}
	hasIndex := len(opts.Index) > 0
	if opts.Skipper == nil {
		opts.Skipper = echo.DefaultSkipper
	}
	check := hasIndex || opts.Browse
	opts.Root, _ = filepath.Abs(opts.Root)
	length := len(opts.Path)
	if length > 0 && opts.Path[0] != '/' {
		opts.Path = `/` + opts.Path
		length++
	}

	log.GetLogger("echo").Debugf("Static: %v\t-> %v", opts.Path, opts.Root)

	return func(next echo.Handler) echo.Handler {
		return echo.HandlerFunc(func(c echo.Context) error {
			if opts.Skipper(c) {
				return next.Handle(c)
			}
			file := c.Request().URL().Path()
			if len(file) < length || file[0:length] != opts.Path {
				return next.Handle(c)
			}
			file = file[length:]
			file = path.Clean(file)
			absFile := filepath.Join(opts.Root, file)
			if !strings.HasPrefix(absFile, opts.Root) {
				return next.Handle(c)
			}
			w := c.Response()
			if !check {
				w.ServeFile(absFile)
				return nil
			}
			fi, err := os.Stat(absFile)
			if err != nil {
				return echo.ErrNotFound
			}
			if fi.IsDir() {
				if hasIndex {
					// Index file
					indexFile := filepath.Join(absFile, opts.Index)
					fi, err = os.Stat(indexFile)
					if err != nil || fi.IsDir() {
						if opts.Browse {
							return listDir(absFile, file, w)
						}
						return echo.ErrNotFound
					}
					absFile = indexFile
				} else {
					if opts.Browse {
						return listDir(absFile, file, w)
					}
					return echo.ErrNotFound
				}
			}
			w.ServeFile(absFile)
			return nil
		})
	}
}

func listDir(absFile string, file string, w engine.Response) error {
	fs := http.Dir(filepath.Dir(absFile))
	d, err := fs.Open(filepath.Base(absFile))
	if err != nil {
		return echo.ErrNotFound
	}
	defer d.Close()
	dirs, err := d.Readdir(-1)
	if err != nil {
		return echo.ErrNotFound
	}

	// Create a directory index
	w.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8)
	if _, err = fmt.Fprintf(w, `<!doctype html>

        <meta charset="UTF-8">
        <title>`+file+`</title>
        <meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible" />
        <meta content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport" />
        <link href="/favicon.ico" rel="shortcut icon">

    `); err != nil {
		return err
	}
	if _, err = fmt.Fprintf(w, "
\n"); err != nil {
		return err
	}
	for _, d := range dirs {
		name := d.Name()
		color := "#212121"
		if d.IsDir() {
			color = "#e91e63"
			name += "/"
		}
		if _, err = fmt.Fprintf(w, "
%s
\n", name, color, name); err != nil {
			return err
		}
	}
	if _, err = fmt.Fprintf(w, "
\n"); err != nil {
		return err
	}
	_, err = fmt.Fprintf(w, "\n")
	return err
}
<|endoftext|>


<commit_before>package oci

import (
	"context"
	"strconv"
	"time"

	"github.com/containerd/containerd/images"
	"github.com/containerd/containerd/images/oci"
	"github.com/docker/distribution/reference"
	"github.com/moby/buildkit/exporter"
	"github.com/moby/buildkit/exporter/containerimage"
	"github.com/moby/buildkit/session"
	"github.com/moby/buildkit/session/filesync"
	"github.com/moby/buildkit/util/dockerexporter"
	"github.com/moby/buildkit/util/progress"
	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/pkg/errors"
)

type ExporterVariant string

const (
	keyImageName  = "name"
	VariantOCI    = "oci"
	VariantDocker = "docker"
	ociTypes      = "oci-mediatypes"
)

type Opt struct {
	SessionManager *session.Manager
	ImageWriter    *containerimage.ImageWriter
	Variant        ExporterVariant
}

type imageExporter struct {
	opt Opt
}

func New(opt Opt) (exporter.Exporter, error) {
	im := &imageExporter{opt: opt}
	return im, nil
}

func normalize(name string) (string, error) {
	if name == "" {
		return "", nil
	}
	parsed, err := reference.ParseNormalizedNamed(name)
	if err != nil {
		return "", errors.Wrapf(err, "failed to parse %s", name)
	}
	return reference.TagNameOnly(parsed).String(), nil
}

func (e *imageExporter) Resolve(ctx context.Context, opt map[string]string) (exporter.ExporterInstance, error) {
	id := session.FromContext(ctx)
	if id == "" {
		return nil, errors.New("could not access local files without session")
	}

	timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
	defer cancel()

	caller, err := e.opt.SessionManager.Get(timeoutCtx, id)
	if err != nil {
		return nil, err
	}

	var ot *bool
	i := &imageExporterInstance{imageExporter: e, caller: caller}
	for k, v := range opt {
		switch k {
		case keyImageName:
			i.name = v
			if i.name != "*" {
				i.name, err = normalize(i.name)
				if err != nil {
					return nil, err
				}
			}
		case ociTypes:
			ot = new(bool)
			if v == "" {
				*ot = true
				continue
			}
			b, err := strconv.ParseBool(v)
			if err != nil {
				return nil, errors.Wrapf(err, "non-bool value specified for %s", k)
			}
			*ot = b
		default:
			if i.meta == nil {
				i.meta = make(map[string][]byte)
			}
			i.meta[k] = []byte(v)
		}
	}
	if ot == nil {
		i.ociTypes = e.opt.Variant == VariantOCI
	} else {
		i.ociTypes = *ot
	}
	return i, nil
}

type imageExporterInstance struct {
	*imageExporter
	meta     map[string][]byte
	caller   session.Caller
	name     string
	ociTypes bool
}

func (e *imageExporterInstance) Name() string {
	return "exporting to oci image format"
}

func (e *imageExporterInstance) Export(ctx context.Context, src exporter.Source) (map[string]string, error) {
	if e.opt.Variant == VariantDocker && len(src.Refs) > 0 {
		return nil, errors.Errorf("docker exporter does not currently support exporting manifest lists")
	}

	if src.Metadata == nil {
		src.Metadata = make(map[string][]byte)
	}
	for k, v := range e.meta {
		src.Metadata[k] = v
	}

	desc, err := e.opt.ImageWriter.Commit(ctx, src, e.ociTypes)
	if err != nil {
		return nil, err
	}
	defer func() {
		e.opt.ImageWriter.ContentStore().Delete(context.TODO(), desc.Digest)
	}()
	if desc.Annotations == nil {
		desc.Annotations = map[string]string{}
	}
	desc.Annotations[ocispec.AnnotationCreated] = time.Now().UTC().Format(time.RFC3339)

	resp := make(map[string]string)

	if n, ok := src.Metadata["image.name"]; e.name == "*" && ok {
		if e.name, err = normalize(string(n)); err != nil {
			return nil, err
		}
	}

	if e.name != "" {
		resp["image.name"] = e.name
	}

	exp, err := getExporter(e.opt.Variant, e.name)
	if err != nil {
		return nil, err
	}

	w, err := filesync.CopyFileWriter(ctx, e.caller)
	if err != nil {
		return nil, err
	}
	report := oneOffProgress(ctx, "sending tarball")
	if err := exp.Export(ctx, e.opt.ImageWriter.ContentStore(), *desc, w); err != nil {
		w.Close()
		return nil, report(err)
	}
	return resp, report(w.Close())
}

func oneOffProgress(ctx context.Context, id string) func(err error) error {
	pw, _, _ := progress.FromContext(ctx)
	now := time.Now()
	st := progress.Status{
		Started: &now,
	}
	pw.Write(id, st)
	return func(err error) error {
		// TODO: set error on status
		now := time.Now()
		st.Completed = &now
		pw.Write(id, st)
		pw.Close()
		return err
	}
}

func getExporter(variant ExporterVariant, name string) (images.Exporter, error) {
	switch variant {
	case VariantOCI:
		if name != "" {
			return nil, errors.New("oci exporter cannot export named image")
		}
		return &oci.V1Exporter{}, nil
	case VariantDocker:
		return &dockerexporter.DockerExporter{Name: name}, nil
	default:
		return nil, errors.Errorf("invalid variant %q", variant)
	}
}
<commit_msg>fix TestExportMultiPlatform failure<commit_after>package oci

import (
	"context"
	"strconv"
	"time"

	"github.com/containerd/containerd/images"
	"github.com/containerd/containerd/images/oci"
	"github.com/docker/distribution/reference"
	"github.com/moby/buildkit/exporter"
	"github.com/moby/buildkit/exporter/containerimage"
	"github.com/moby/buildkit/session"
	"github.com/moby/buildkit/session/filesync"
	"github.com/moby/buildkit/util/dockerexporter"
	"github.com/moby/buildkit/util/progress"
	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/pkg/errors"
)

type ExporterVariant string

const (
	keyImageName  = "name"
	VariantOCI    = "oci"
	VariantDocker = "docker"
	ociTypes      = "oci-mediatypes"
)

type Opt struct {
	SessionManager *session.Manager
	ImageWriter    *containerimage.ImageWriter
	Variant        ExporterVariant
}

type imageExporter struct {
	opt Opt
}

func New(opt Opt) (exporter.Exporter, error) {
	im := &imageExporter{opt: opt}
	return im, nil
}

func normalize(name string) (string, error) {
	if name == "" {
		return "", nil
	}
	parsed, err := reference.ParseNormalizedNamed(name)
	if err != nil {
		return "", errors.Wrapf(err, "failed to parse %s", name)
	}
	return reference.TagNameOnly(parsed).String(), nil
}

func (e *imageExporter) Resolve(ctx context.Context, opt map[string]string) (exporter.ExporterInstance, error) {
	id := session.FromContext(ctx)
	if id == "" {
		return nil, errors.New("could not access local files without session")
	}

	timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
	defer cancel()

	caller, err := e.opt.SessionManager.Get(timeoutCtx, id)
	if err != nil {
		return nil, err
	}

	var ot *bool
	i := &imageExporterInstance{imageExporter: e, caller: caller}
	for k, v := range opt {
		switch k {
		case keyImageName:
			i.name = v
			if i.name != "*" {
				i.name, err = normalize(i.name)
				if err != nil {
					return nil, err
				}
			}
		case ociTypes:
			ot = new(bool)
			if v == "" {
				*ot = true
				continue
			}
			b, err := strconv.ParseBool(v)
			if err != nil {
				return nil, errors.Wrapf(err, "non-bool value specified for %s", k)
			}
			*ot = b
		default:
			if i.meta == nil {
				i.meta = make(map[string][]byte)
			}
			i.meta[k] = []byte(v)
		}
	}
	if ot == nil {
		i.ociTypes = e.opt.Variant == VariantOCI
	} else {
		i.ociTypes = *ot
	}
	return i, nil
}

type imageExporterInstance struct {
	*imageExporter
	meta     map[string][]byte
	caller   session.Caller
	name     string
	ociTypes bool
}

func (e *imageExporterInstance) Name() string {
	return "exporting to oci image format"
}

func (e *imageExporterInstance) Export(ctx context.Context, src exporter.Source) (map[string]string, error) {
	if e.opt.Variant == VariantDocker && len(src.Refs) > 0 {
		return nil, errors.Errorf("docker exporter does not currently support exporting manifest lists")
	}

	if src.Metadata == nil {
		src.Metadata = make(map[string][]byte)
	}
	for k, v := range e.meta {
		src.Metadata[k] = v
	}

	desc, err := e.opt.ImageWriter.Commit(ctx, src, e.ociTypes)
	if err != nil {
		return nil, err
	}
	defer func() {
		e.opt.ImageWriter.ContentStore().Delete(context.TODO(), desc.Digest)
	}()
	if desc.Annotations == nil {
		desc.Annotations = map[string]string{}
	}
	desc.Annotations[ocispec.AnnotationCreated] = time.Now().UTC().Format(time.RFC3339)

	resp := make(map[string]string)

	if n, ok := src.Metadata["image.name"]; e.name == "*" && ok {
		if e.name, err = normalize(string(n)); err != nil {
			return nil, err
		}
	}

	if e.name != "" {
		resp["image.name"] = e.name
	}

	exp, err := getExporter(e.opt.Variant, e.name)
	if err != nil {
		return nil, err
	}

	w, err := filesync.CopyFileWriter(ctx, e.caller)
	if err != nil {
		return nil, err
	}
	report := oneOffProgress(ctx, "sending tarball")
	if err := exp.Export(ctx, e.opt.ImageWriter.ContentStore(), *desc, w); err != nil {
		w.Close()
		return nil, report(err)
	}
	return resp, report(w.Close())
}

func oneOffProgress(ctx context.Context, id string) func(err error) error {
	pw, _, _ := progress.FromContext(ctx)
	now := time.Now()
	st := progress.Status{
		Started: &now,
	}
	pw.Write(id, st)
	return func(err error) error {
		// TODO: set error on status
		now := time.Now()
		st.Completed = &now
		pw.Write(id, st)
		pw.Close()
		return err
	}
}

func getExporter(variant ExporterVariant, name string) (images.Exporter, error) {
	switch variant {
	case VariantOCI:
		if name != "" {
			return nil, errors.New("oci exporter cannot export named image")
		}
		return oci.ResolveV1ExportOpt(oci.WithAllPlatforms(true))
	case VariantDocker:
		return &dockerexporter.DockerExporter{Name: name}, nil
	default:
		return nil, errors.Errorf("invalid variant %q", variant)
	}
}
<|endoftext|>


<commit_before>// Package freeport provides a helper for allocating free ports across multiple
// processes on the same machine.
package freeport

import (
	"container/list"
	"fmt"
	"math/rand"
	"net"
	"os"
	"runtime"
	"sync"
	"time"
)

const (
	// maxBlocks is the number of available port blocks before exclusions.
	maxBlocks = 30

	// lowPort is the lowest port number that should be used.
	lowPort = 10000

	// attempts is how often we try to allocate a port block
	// before giving up.
	attempts = 10
)

var (
	// blockSize is the size of the allocated port block. ports are given out
	// consecutively from that block and after that point in a LRU fashion.
	blockSize int

	// effectiveMaxBlocks is the number of available port blocks.
	// lowPort + effectiveMaxBlocks * blockSize must be less than 65535.
	effectiveMaxBlocks int

	// firstPort is the first port of the allocated block.
	firstPort int

	// lockLn is the system-wide mutex for the port block.
	lockLn net.Listener

	// mu guards:
	// - pendingPorts
	// - freePorts
	// - total
	mu sync.Mutex

	// once is used to do the initialization on the first call to retrieve free
	// ports
	once sync.Once

	// condNotEmpty is a condition variable to wait for freePorts to be not
	// empty. Linked to 'mu'
	condNotEmpty *sync.Cond

	// freePorts is a FIFO of all currently free ports. Take from the front,
	// and return to the back.
	freePorts *list.List

	// pendingPorts is a FIFO of recently freed ports that have not yet passed
	// the not-in-use check.
	pendingPorts *list.List

	// total is the total number of available ports in the block for use.
	total int

	// stopCh is used to signal to background goroutines to terminate. Only
	// really exists for the safety of reset() during unit tests.
	stopCh chan struct{}

	// stopWg is used to keep track of background goroutines that are still
	// alive. Only really exists for the safety of reset() during unit tests.
	stopWg sync.WaitGroup
)

// initialize is used to initialize freeport.
func initialize() {
	var err error

	blockSize = 1500
	limit, err := systemLimit()
	if err != nil {
		panic("freeport: error getting system limit: " + err.Error())
	}
	if limit > 0 && limit < blockSize {
		logf("INFO", "blockSize %d too big for system limit %d. Adjusting...", blockSize, limit)
		blockSize = limit - 3
	}

	effectiveMaxBlocks, err = adjustMaxBlocks()
	if err != nil {
		panic("freeport: ephemeral port range detection failed: " + err.Error())
	}
	if effectiveMaxBlocks < 0 {
		panic("freeport: no blocks of ports available outside of ephemeral range")
	}
	if lowPort+effectiveMaxBlocks*blockSize > 65535 {
		panic("freeport: block size too big or too many blocks requested")
	}

	rand.Seed(time.Now().UnixNano())
	firstPort, lockLn = alloc()

	condNotEmpty = sync.NewCond(&mu)
	freePorts = list.New()
	pendingPorts = list.New()

	// fill with all available free ports
	for port := firstPort + 1; port < firstPort+blockSize; port++ {
		if used := isPortInUse(port); !used {
			freePorts.PushBack(port)
		}
	}
	total = freePorts.Len()

	stopWg.Add(1)
	stopCh = make(chan struct{})
	// Note: we pass this param explicitly to the goroutine so that we can
	// freely recreate the underlying stop channel during reset() after closing
	// the original.
	go checkFreedPorts(stopCh)
}

func shutdownGoroutine() {
	mu.Lock()
	if stopCh == nil {
		mu.Unlock()
		return
	}

	close(stopCh)
	stopCh = nil
	mu.Unlock()

	stopWg.Wait()
}

// reset will reverse the setup from initialize() and then redo it (for tests)
func reset() {
	logf("INFO", "resetting the freeport package state")
	shutdownGoroutine()

	mu.Lock()
	defer mu.Unlock()

	effectiveMaxBlocks = 0
	firstPort = 0
	if lockLn != nil {
		lockLn.Close()
		lockLn = nil
	}

	once = sync.Once{}

	freePorts = nil
	pendingPorts = nil
	total = 0
}

func checkFreedPorts(stopCh <-chan struct{}) {
	defer stopWg.Done()

	ticker := time.NewTicker(250 * time.Millisecond)
	for {
		select {
		case <-stopCh:
			logf("INFO", "Closing checkFreedPorts()")
			return
		case <-ticker.C:
			checkFreedPortsOnce()
		}
	}
}

func checkFreedPortsOnce() {
	mu.Lock()
	defer mu.Unlock()

	pending := pendingPorts.Len()
	remove := make([]*list.Element, 0, pending)
	for elem := pendingPorts.Front(); elem != nil; elem = elem.Next() {
		port := elem.Value.(int)
		if used := isPortInUse(port); !used {
			freePorts.PushBack(port)
			remove = append(remove, elem)
		}
	}

	retained := pending - len(remove)

	if retained > 0 {
		logf("WARN", "%d out of %d pending ports are still in use; something probably didn't wait around for the port to be closed!", retained, pending)
	}

	if len(remove) == 0 {
		return
	}

	for _, elem := range remove {
		pendingPorts.Remove(elem)
	}

	condNotEmpty.Broadcast()
}

// adjustMaxBlocks avoids having the allocation ranges overlap the ephemeral
// port range.
func adjustMaxBlocks() (int, error) {
	ephemeralPortMin, ephemeralPortMax, err := getEphemeralPortRange()
	if err != nil {
		return 0, err
	}

	if ephemeralPortMin <= 0 || ephemeralPortMax <= 0 {
		logf("INFO", "ephemeral port range detection not configured for GOOS=%q", runtime.GOOS)
		return maxBlocks, nil
	}

	logf("INFO", "detected ephemeral port range of [%d, %d]", ephemeralPortMin, ephemeralPortMax)
	for block := 0; block < maxBlocks; block++ {
		min := lowPort + block*blockSize
		max := min + blockSize
		overlap := intervalOverlap(min, max-1, ephemeralPortMin, ephemeralPortMax)
		if overlap {
			logf("INFO", "reducing max blocks from %d to %d to avoid the ephemeral port range", maxBlocks, block)
			return block, nil
		}
	}
	return maxBlocks, nil
}

// alloc reserves a port block for exclusive use for the lifetime of the
// application. lockLn serves as a system-wide mutex for the port block and is
// implemented as a TCP listener which is bound to the firstPort and which will
// be automatically released when the application terminates.
func alloc() (int, net.Listener) {
	for i := 0; i < attempts; i++ {
		block := int(rand.Int31n(int32(effectiveMaxBlocks)))
		firstPort := lowPort + block*blockSize
		ln, err := net.ListenTCP("tcp", tcpAddr("127.0.0.1", firstPort))
		if err != nil {
			continue
		}
		// logf("DEBUG", "allocated port block %d (%d-%d)", block, firstPort, firstPort+blockSize-1)
		return firstPort, ln
	}
	panic("freeport: cannot allocate port block")
}

// MustTake is the same as Take except it panics on error.
//
// Deprecated: Use GetN or Port instead.
func MustTake(n int) (ports []int) {
	ports, err := Take(n)
	if err != nil {
		panic(err)
	}
	return ports
}

// Take returns a list of free ports from the allocated port block. It is safe
// to call this method concurrently. Ports have been tested to be available on
// 127.0.0.1 TCP but there is no guarantee that they will remain free in the
// future.
//
// Most callers should prefer GetN or Port.
func Take(n int) (ports []int, err error) {
	if n <= 0 {
		return nil, fmt.Errorf("freeport: cannot take %d ports", n)
	}

	mu.Lock()
	defer mu.Unlock()

	// Reserve a port block
	once.Do(initialize)

	if n > total {
		return nil, fmt.Errorf("freeport: block size too small")
	}

	for len(ports) < n {
		for freePorts.Len() == 0 {
			if total == 0 {
				return nil, fmt.Errorf("freeport: impossible to satisfy request; there are no actual free ports in the block anymore")
			}
			condNotEmpty.Wait()
		}

		elem := freePorts.Front()
		freePorts.Remove(elem)
		port := elem.Value.(int)

		if used := isPortInUse(port); used {
			// Something outside of the test suite has stolen this port, possibly
			// due to assignment to an ephemeral port, remove it completely.
			logf("WARN", "leaked port %d due to theft; removing from circulation", port)
			total--
			continue
		}

		ports = append(ports, port)
	}

	// logf("DEBUG", "free ports: %v", ports)
	return ports, nil
}

// peekFree returns the next port that will be returned by Take to aid in testing.
func peekFree() int {
	mu.Lock()
	defer mu.Unlock()
	return freePorts.Front().Value.(int)
}

// peekAllFree returns all free ports that could be returned by Take to aid in testing.
func peekAllFree() []int {
	mu.Lock()
	defer mu.Unlock()

	var out []int
	for elem := freePorts.Front(); elem != nil; elem = elem.Next() {
		port := elem.Value.(int)
		out = append(out, port)
	}

	return out
}

// stats returns diagnostic data to aid in testing
func stats() (numTotal, numPending, numFree int) {
	mu.Lock()
	defer mu.Unlock()
	return total, pendingPorts.Len(), freePorts.Len()
}

// Return returns a block of ports back to the general pool. These ports should
// have been returned from a call to Take().
func Return(ports []int) {
	if len(ports) == 0 {
		return // convenience short circuit for test ergonomics
	}

	mu.Lock()
	defer mu.Unlock()

	for _, port := range ports {
		if port > firstPort && port < firstPort+blockSize {
			pendingPorts.PushBack(port)
		}
	}
}

func isPortInUse(port int) bool {
	ln, err := net.ListenTCP("tcp", tcpAddr("127.0.0.1", port))
	if err != nil {
		return true
	}
	ln.Close()
	return false
}

func tcpAddr(ip string, port int) *net.TCPAddr {
	return &net.TCPAddr{IP: net.ParseIP(ip), Port: port}
}

// intervalOverlap returns true if the doubly-inclusive integer intervals
// represented by [min1, max1] and [min2, max2] overlap.
func intervalOverlap(min1, max1, min2, max2 int) bool {
	if min1 > max1 {
		logf("WARN", "interval1 is not ordered [%d, %d]", min1, max1)
		return false
	}
	if min2 > max2 {
		logf("WARN", "interval2 is not ordered [%d, %d]", min2, max2)
		return false
	}
	return min1 <= max2 && min2 <= max1
}

func logf(severity string, format string, a ...interface{}) {
	fmt.Fprintf(os.Stderr, "["+severity+"] freeport: "+format+"\n", a...)
}

type TestingT interface {
	Helper()
	Fatalf(format string, args ...interface{})
	Cleanup(func())
}

// GetN returns n free ports from the allocated port block, and returns the
// ports to the pool when the test ends. See Take for more details.
func GetN(t TestingT, n int) []int {
	t.Helper()
	ports, err := Take(n)
	if err != nil {
		t.Fatalf("failed to take %v ports: %w", n, err)
	}
	t.Cleanup(func() {
		Return(ports)
	})
	return ports
}

// Port returns a single free port from the allocated port block, and returns the
// port to the pool when the test ends. See Take for more details.
// Use GetN if more than a single port is required.
func Port(t TestingT) int {
	t.Helper()
	return GetN(t, 1)[0]
}

// Deprecated: Please use Take/Return calls instead.
func Get(n int) (ports []int) { return MustTake(n) }

// Deprecated: Please use Take/Return calls instead.
func GetT(t TestingT, n int) (ports []int) { return MustTake(n) }

// Deprecated: Please use Take/Return calls instead.
func Free(n int) (ports []int, err error) { return MustTake(n), nil }
<commit_msg>sdk: upgrade freeport godoc<commit_after>// Package freeport provides a helper for reserving free TCP ports across multiple
// processes on the same machine. Each process reserves a block of ports outside
// the ephemeral port range. Tests can request one of these reserved ports
// and freeport will ensure that no other test uses that port until it is returned
// to freeport.
//
// Freeport is particularly useful when the code being tested does not accept
// a net.Listener. Any code that accepts a net.Listener (or uses net/http/httptest.Server)
// can use port 0 (ex: 127.0.0.1:0) to find an unused ephemeral port that will
// not conflict.
//
// Any code that does not accept a net.Listener or can not bind directly to port
// zero should use freeport to find an unused port.
package freeport

import (
	"container/list"
	"fmt"
	"math/rand"
	"net"
	"os"
	"runtime"
	"sync"
	"time"
)

const (
	// maxBlocks is the number of available port blocks before exclusions.
	maxBlocks = 30

	// lowPort is the lowest port number that should be used.
	lowPort = 10000

	// attempts is how often we try to allocate a port block
	// before giving up.
	attempts = 10
)

var (
	// blockSize is the size of the allocated port block. ports are given out
	// consecutively from that block and after that point in a LRU fashion.
	blockSize int

	// effectiveMaxBlocks is the number of available port blocks.
	// lowPort + effectiveMaxBlocks * blockSize must be less than 65535.
	effectiveMaxBlocks int

	// firstPort is the first port of the allocated block.
	firstPort int

	// lockLn is the system-wide mutex for the port block.
	lockLn net.Listener

	// mu guards:
	// - pendingPorts
	// - freePorts
	// - total
	mu sync.Mutex

	// once is used to do the initialization on the first call to retrieve free
	// ports
	once sync.Once

	// condNotEmpty is a condition variable to wait for freePorts to be not
	// empty. Linked to 'mu'
	condNotEmpty *sync.Cond

	// freePorts is a FIFO of all currently free ports. Take from the front,
	// and return to the back.
	freePorts *list.List

	// pendingPorts is a FIFO of recently freed ports that have not yet passed
	// the not-in-use check.
	pendingPorts *list.List

	// total is the total number of available ports in the block for use.
	total int

	// stopCh is used to signal to background goroutines to terminate. Only
	// really exists for the safety of reset() during unit tests.
	stopCh chan struct{}

	// stopWg is used to keep track of background goroutines that are still
	// alive. Only really exists for the safety of reset() during unit tests.
	stopWg sync.WaitGroup
)

// initialize is used to initialize freeport.
func initialize() {
	var err error

	blockSize = 1500
	limit, err := systemLimit()
	if err != nil {
		panic("freeport: error getting system limit: " + err.Error())
	}
	if limit > 0 && limit < blockSize {
		logf("INFO", "blockSize %d too big for system limit %d. Adjusting...", blockSize, limit)
		blockSize = limit - 3
	}

	effectiveMaxBlocks, err = adjustMaxBlocks()
	if err != nil {
		panic("freeport: ephemeral port range detection failed: " + err.Error())
	}
	if effectiveMaxBlocks < 0 {
		panic("freeport: no blocks of ports available outside of ephemeral range")
	}
	if lowPort+effectiveMaxBlocks*blockSize > 65535 {
		panic("freeport: block size too big or too many blocks requested")
	}

	rand.Seed(time.Now().UnixNano())
	firstPort, lockLn = alloc()

	condNotEmpty = sync.NewCond(&mu)
	freePorts = list.New()
	pendingPorts = list.New()

	// fill with all available free ports
	for port := firstPort + 1; port < firstPort+blockSize; port++ {
		if used := isPortInUse(port); !used {
			freePorts.PushBack(port)
		}
	}
	total = freePorts.Len()

	stopWg.Add(1)
	stopCh = make(chan struct{})
	// Note: we pass this param explicitly to the goroutine so that we can
	// freely recreate the underlying stop channel during reset() after closing
	// the original.
	go checkFreedPorts(stopCh)
}

func shutdownGoroutine() {
	mu.Lock()
	if stopCh == nil {
		mu.Unlock()
		return
	}

	close(stopCh)
	stopCh = nil
	mu.Unlock()

	stopWg.Wait()
}

// reset will reverse the setup from initialize() and then redo it (for tests)
func reset() {
	logf("INFO", "resetting the freeport package state")
	shutdownGoroutine()

	mu.Lock()
	defer mu.Unlock()

	effectiveMaxBlocks = 0
	firstPort = 0
	if lockLn != nil {
		lockLn.Close()
		lockLn = nil
	}

	once = sync.Once{}

	freePorts = nil
	pendingPorts = nil
	total = 0
}

func checkFreedPorts(stopCh <-chan struct{}) {
	defer stopWg.Done()

	ticker := time.NewTicker(250 * time.Millisecond)
	for {
		select {
		case <-stopCh:
			logf("INFO", "Closing checkFreedPorts()")
			return
		case <-ticker.C:
			checkFreedPortsOnce()
		}
	}
}

func checkFreedPortsOnce() {
	mu.Lock()
	defer mu.Unlock()

	pending := pendingPorts.Len()
	remove := make([]*list.Element, 0, pending)
	for elem := pendingPorts.Front(); elem != nil; elem = elem.Next() {
		port := elem.Value.(int)
		if used := isPortInUse(port); !used {
			freePorts.PushBack(port)
			remove = append(remove, elem)
		}
	}

	retained := pending - len(remove)

	if retained > 0 {
		logf("WARN", "%d out of %d pending ports are still in use; something probably didn't wait around for the port to be closed!", retained, pending)
	}

	if len(remove) == 0 {
		return
	}

	for _, elem := range remove {
		pendingPorts.Remove(elem)
	}

	condNotEmpty.Broadcast()
}

// adjustMaxBlocks avoids having the allocation ranges overlap the ephemeral
// port range.
func adjustMaxBlocks() (int, error) {
	ephemeralPortMin, ephemeralPortMax, err := getEphemeralPortRange()
	if err != nil {
		return 0, err
	}

	if ephemeralPortMin <= 0 || ephemeralPortMax <= 0 {
		logf("INFO", "ephemeral port range detection not configured for GOOS=%q", runtime.GOOS)
		return maxBlocks, nil
	}

	logf("INFO", "detected ephemeral port range of [%d, %d]", ephemeralPortMin, ephemeralPortMax)
	for block := 0; block < maxBlocks; block++ {
		min := lowPort + block*blockSize
		max := min + blockSize
		overlap := intervalOverlap(min, max-1, ephemeralPortMin, ephemeralPortMax)
		if overlap {
			logf("INFO", "reducing max blocks from %d to %d to avoid the ephemeral port range", maxBlocks, block)
			return block, nil
		}
	}
	return maxBlocks, nil
}

// alloc reserves a port block for exclusive use for the lifetime of the
// application. lockLn serves as a system-wide mutex for the port block and is
// implemented as a TCP listener which is bound to the firstPort and which will
// be automatically released when the application terminates.
func alloc() (int, net.Listener) {
	for i := 0; i < attempts; i++ {
		block := int(rand.Int31n(int32(effectiveMaxBlocks)))
		firstPort := lowPort + block*blockSize
		ln, err := net.ListenTCP("tcp", tcpAddr("127.0.0.1", firstPort))
		if err != nil {
			continue
		}
		// logf("DEBUG", "allocated port block %d (%d-%d)", block, firstPort, firstPort+blockSize-1)
		return firstPort, ln
	}
	panic("freeport: cannot allocate port block")
}

// MustTake is the same as Take except it panics on error.
//
// Deprecated: Use GetN or Port instead.
func MustTake(n int) (ports []int) {
	ports, err := Take(n)
	if err != nil {
		panic(err)
	}
	return ports
}

// Take returns a list of free ports from the reserved port block. It is safe
// to call this method concurrently. Ports have been tested to be available on
// 127.0.0.1 TCP but there is no guarantee that they will remain free in the
// future.
//
// Most callers should prefer GetN or Port.
func Take(n int) (ports []int, err error) {
	if n <= 0 {
		return nil, fmt.Errorf("freeport: cannot take %d ports", n)
	}

	mu.Lock()
	defer mu.Unlock()

	// Reserve a port block
	once.Do(initialize)

	if n > total {
		return nil, fmt.Errorf("freeport: block size too small")
	}

	for len(ports) < n {
		for freePorts.Len() == 0 {
			if total == 0 {
				return nil, fmt.Errorf("freeport: impossible to satisfy request; there are no actual free ports in the block anymore")
			}
			condNotEmpty.Wait()
		}

		elem := freePorts.Front()
		freePorts.Remove(elem)
		port := elem.Value.(int)

		if used := isPortInUse(port); used {
			// Something outside of the test suite has stolen this port, possibly
			// due to assignment to an ephemeral port, remove it completely.
			logf("WARN", "leaked port %d due to theft; removing from circulation", port)
			total--
			continue
		}

		ports = append(ports, port)
	}

	// logf("DEBUG", "free ports: %v", ports)
	return ports, nil
}

// peekFree returns the next port that will be returned by Take to aid in testing.
func peekFree() int {
	mu.Lock()
	defer mu.Unlock()
	return freePorts.Front().Value.(int)
}

// peekAllFree returns all free ports that could be returned by Take to aid in testing.
func peekAllFree() []int {
	mu.Lock()
	defer mu.Unlock()

	var out []int
	for elem := freePorts.Front(); elem != nil; elem = elem.Next() {
		port := elem.Value.(int)
		out = append(out, port)
	}

	return out
}

// stats returns diagnostic data to aid in testing
func stats() (numTotal, numPending, numFree int) {
	mu.Lock()
	defer mu.Unlock()
	return total, pendingPorts.Len(), freePorts.Len()
}

// Return returns a block of ports back to the general pool. These ports should
// have been returned from a call to Take().
func Return(ports []int) {
	if len(ports) == 0 {
		return // convenience short circuit for test ergonomics
	}

	mu.Lock()
	defer mu.Unlock()

	for _, port := range ports {
		if port > firstPort && port < firstPort+blockSize {
			pendingPorts.PushBack(port)
		}
	}
}

func isPortInUse(port int) bool {
	ln, err := net.ListenTCP("tcp", tcpAddr("127.0.0.1", port))
	if err != nil {
		return true
	}
	ln.Close()
	return false
}

func tcpAddr(ip string, port int) *net.TCPAddr {
	return &net.TCPAddr{IP: net.ParseIP(ip), Port: port}
}

// intervalOverlap returns true if the doubly-inclusive integer intervals
// represented by [min1, max1] and [min2, max2] overlap.
func intervalOverlap(min1, max1, min2, max2 int) bool {
	if min1 > max1 {
		logf("WARN", "interval1 is not ordered [%d, %d]", min1, max1)
		return false
	}
	if min2 > max2 {
		logf("WARN", "interval2 is not ordered [%d, %d]", min2, max2)
		return false
	}
	return min1 <= max2 && min2 <= max1
}

func logf(severity string, format string, a ...interface{}) {
	fmt.Fprintf(os.Stderr, "["+severity+"] freeport: "+format+"\n", a...)
}

// TestingT is the minimal set of methods implemented by *testing.T that are
// used by functions in freelist.
//
// In the future new methods may be added to this interface, but those methods
// should always be implemented by *testing.T
type TestingT interface {
	Helper()
	Fatalf(format string, args ...interface{})
	Cleanup(func())
}

// GetN returns n free ports from the reserved port block, and returns the
// ports to the pool when the test ends. See Take for more details.
func GetN(t TestingT, n int) []int {
	t.Helper()
	ports, err := Take(n)
	if err != nil {
		t.Fatalf("failed to take %v ports: %w", n, err)
	}
	t.Cleanup(func() {
		Return(ports)
	})
	return ports
}

// Port returns a single free port from the reserved port block, and returns the
// port to the pool when the test ends. See Take for more details.
// Use GetN if more than a single port is required.
func Port(t TestingT) int {
	t.Helper()
	return GetN(t, 1)[0]
}

// Deprecated: Please use Take/Return calls instead.
func Get(n int) (ports []int) { return MustTake(n) }

// Deprecated: Please use Take/Return calls instead.
func GetT(t TestingT, n int) (ports []int) { return MustTake(n) }

// Deprecated: Please use Take/Return calls instead.
func Free(n int) (ports []int, err error) { return MustTake(n), nil }
<|endoftext|>


<commit_before>package secureclip

import (
	"sync"
	"time"

	"github.com/atotto/clipboard"
)

var (
	lastClip     = time.Now()
	lastClipLock sync.Mutex
	clipTimeout  = time.Second * 30
)

func getLastClip() time.Time {
	lastClipLock.Lock()
	t := lastClip
	lastClipLock.Unlock()
	return t
}

func setLastClip(t time.Time) {
	lastClipLock.Lock()
	lastClip = t
	lastClipLock.Unlock()
}

// Clip copies the passphrase given by `passphrase` to the clipboard. The
// clipboard will be cleared 30 seconds after the last `Clip` call.
func Clip(passphrase string) error {
	err := clipboard.WriteAll(passphrase)
	if err != nil {
		return err
	}
	setLastClip(time.Now())
	go func() {
		time.Sleep(clipTimeout)
		if time.Since(getLastClip()) > clipTimeout {
			clipboard.WriteAll("")
		}
	}()
	return nil
}

// Clear clears the clipboard.
func Clear() error {
	return clipboard.WriteAll("")
}
<commit_msg>replace mutex secureclip implementation with sync/atomic<commit_after>package secureclip

import (
	"sync/atomic"
	"time"

	"github.com/atotto/clipboard"
)

var (
	lastClip    = time.Now().Unix()
	clipTimeout = time.Second * 30
)

// Clip copies the passphrase given by `passphrase` to the clipboard. The
// clipboard will be cleared 30 seconds after the last `Clip` call.
func Clip(passphrase string) error {
	err := clipboard.WriteAll(passphrase)
	if err != nil {
		return err
	}
	atomic.StoreInt64(&lastClip, time.Now().Unix())
	go func() {
		time.Sleep(clipTimeout)
		lc := atomic.LoadInt64(&lastClip)
		if time.Since(time.Unix(lc, 0)) > clipTimeout {
			clipboard.WriteAll("")
		}
	}()
	return nil
}

// Clear clears the clipboard.
func Clear() error {
	return clipboard.WriteAll("")
}
<|endoftext|>


<commit_before>package file

import (
	"os"
	"path/filepath"
	"syscall"
	"time"
)

// winExFileInfo stores windows specific file information.
// At the moment all the information we need is available
// through the Sys() interface.
type winExFileInfo struct {
	os.FileInfo
	fid  FID
	path string
}

// CTime returns the CreationTime from Win32FileAttributeData.
func (fi *winExFileInfo) CTime() time.Time {
	return time.Unix(0, fi.Sys().(syscall.Win32FileAttributeData).CreationTime)
}

// ATime returns the LastAccessTime from Win32FileAttributeData.
func (fi *winExFileInfo) ATime() time.Time {
	return time.Unix(0, fi.Sys().(syscall.Win32FileAttributeData).LastAccessTime)
}

// FID returns the windows version of a file id. The FID for Windows
// is the VolumeSerialNumber (IDHigh) and the FileIndexHigh/Low (IDLow)
func (fi *winExFileInfo) FID() FID {
	return fi.fid
}

// Path returns the full path for the file.
func (fi *winExFileInfo) Path() string {
	return fi.path
}

// newExFileInfo creates a new winExFileInfo from a os.FileInfo.
func newExFileInfo(fi os.FileInfo, path string) *winExFileInfo {
	fid, err := createFID(path)
	if err != nil {
		// do something
	}

	absolute, _ := filepath.Abs(path)
	return &winExFileInfo{
		FileInfo: fi,
		fid:      fid,
		path:     filepath.Clean(absolute),
	}
}

// createFID creates the file by making a windows specific system call
// to retrieve the VolumeSerialNumber and FileIndexHigh/Low. Unfortunately
// these values are not exposed through the Sys() in FileInfo. The code
// for making these calls is a slightly modified version of the code in
// the go os package types_windows.go file.
func createFID(path string) (FID, error) {
	fid := FID{}
	pathp, err := syscall.UTF16PtrFromString(path)
	if err != nil {
		return fid, err
	}
	h, err := syscall.CreateFile(pathp, 0, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
	if err != nil {
		return fid, err
	}
	defer syscall.CloseHandle(h)
	var handleInfo syscall.ByHandleFileInformation
	err = syscall.GetFileInformationByHandle(syscall.Handle(h), &handleInfo)
	if err != nil {
		return fid, err
	}
	fid.IDHigh = uint64(handleInfo.VolumeSerialNumber)
	fid.IDLow = uint64(handleInfo.FileIndexHigh)<<32 + uint64(handleInfo.FileIndexLow)
	return fid, nil
}
<commit_msg>Get Nanoseconds for time.<commit_after>package file

import (
	"os"
	"path/filepath"
	"syscall"
	"time"
)

// winExFileInfo stores windows specific file information.
// At the moment all the information we need is available
// through the Sys() interface.
type winExFileInfo struct {
	os.FileInfo
	fid  FID
	path string
}

// CTime returns the CreationTime from Win32FileAttributeData.
func (fi *winExFileInfo) CTime() time.Time {
	return time.Unix(0, fi.Sys().(syscall.Win32FileAttributeData).CreationTime.Nanoseconds())
}

// ATime returns the LastAccessTime from Win32FileAttributeData.
func (fi *winExFileInfo) ATime() time.Time {
	return time.Unix(0, fi.Sys().(syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
}

// FID returns the windows version of a file id. The FID for Windows
// is the VolumeSerialNumber (IDHigh) and the FileIndexHigh/Low (IDLow)
func (fi *winExFileInfo) FID() FID {
	return fi.fid
}

// Path returns the full path for the file.
func (fi *winExFileInfo) Path() string {
	return fi.path
}

// newExFileInfo creates a new winExFileInfo from a os.FileInfo.
func newExFileInfo(fi os.FileInfo, path string) *winExFileInfo {
	fid, err := createFID(path)
	if err != nil {
		// do something
	}

	absolute, _ := filepath.Abs(path)
	return &winExFileInfo{
		FileInfo: fi,
		fid:      fid,
		path:     filepath.Clean(absolute),
	}
}

// createFID creates the file by making a windows specific system call
// to retrieve the VolumeSerialNumber and FileIndexHigh/Low. Unfortunately
// these values are not exposed through the Sys() in FileInfo. The code
// for making these calls is a slightly modified version of the code in
// the go os package types_windows.go file.
func createFID(path string) (FID, error) {
	fid := FID{}
	pathp, err := syscall.UTF16PtrFromString(path)
	if err != nil {
		return fid, err
	}
	h, err := syscall.CreateFile(pathp, 0, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
	if err != nil {
		return fid, err
	}
	defer syscall.CloseHandle(h)
	var handleInfo syscall.ByHandleFileInformation
	err = syscall.GetFileInformationByHandle(syscall.Handle(h), &handleInfo)
	if err != nil {
		return fid, err
	}
	fid.IDHigh = uint64(handleInfo.VolumeSerialNumber)
	fid.IDLow = uint64(handleInfo.FileIndexHigh)<<32 + uint64(handleInfo.FileIndexLow)
	return fid, nil
}
<|endoftext|>