Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(89)

Unified Diff: common/system/prober/probe.go

Issue 2932443002: Initial transfer. (Closed)
Patch Set: move to system Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | common/system/prober/probe_test.go » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: common/system/prober/probe.go
diff --git a/common/system/prober/probe.go b/common/system/prober/probe.go
new file mode 100644
index 0000000000000000000000000000000000000000..3965f90c863ad9581fcccc3998ea39d0fb91cafa
--- /dev/null
+++ b/common/system/prober/probe.go
@@ -0,0 +1,265 @@
+// Copyright 2017 The LUCI Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Package prober exports Probe, which implements logic to identify a wrapper's
+// wrapped target. In addition to basic PATH/filename lookup, Prober contains
+// logic to ensure that the wrapper is not the same software as the current
+// running instance, and enables optional hard-coded wrap target paths and
+// runtime checks.
+package prober
+
+import (
+ "os"
+ "path/filepath"
+ "strings"
+
+ "golang.org/x/net/context"
+
+ "github.com/luci/luci-go/common/errors"
+ "github.com/luci/luci-go/common/logging"
+ "github.com/luci/luci-go/common/system/environ"
+ "github.com/luci/luci-go/common/system/filesystem"
+)
+
+// CheckWrapperFunc is an optional function that can be implemented for a
+// Prober to check if a candidate path is a wrapper.
+type CheckWrapperFunc func(c context.Context, path string, env environ.Env) (isWrapper bool, err error)
+
+// Probe can Locate a Target executable by probing the local system PATH.
+//
+// Target should be an executable name resolvable by exec.LookPath. On
+// Windows systems, this may omit the executable extension (e.g., "bat", "exe")
+// since that is augmented via the PATHEXT environment variable (see
+// "probe_windows.go").
+type Probe struct {
+ // Target is the name of the target (as seen by exec.LookPath) that we are
+ // searching for.
+ Target string
+
+ // RelativePathOverride is a series of forward-slash-delimited paths to
+ // directories relative to the wrapper executable that will be checked
+ // prior to checking PATH. This allows bundles (e.g., CIPD) that include both
+ // the wrapper and a real implementation, to force the wrapper to use
+ // the bundled implementation.
+ RelativePathOverride []string
+
+ // CheckWrapper, if not nil, is a function called on a candidate wrapper to
+ // determine whether or not that candidate is valid.
+ //
+ // On success, it will return isWrapper, which will be true if path is a
+ // wrapper instance and false if it is not. If an error occurred during
+ // checking, the error should be returned and isWrapper will be ignored. If
+ // a candidate is a wrapper, or if an error occurred during check, the
+ // candidate will be discarded and the probe will continue.
+ //
+ // CheckWrapper should be lightweight and fast, as it may be called multiple
+ // times.
+ CheckWrapper CheckWrapperFunc
+
+ // Self and Selfstat are resolved contain the path and FileInfo of the
+ // currently running executable, respectively. They can both be resolved via
+ // ResolveSelf, and may be empty if resolution has not been performed, or if
+ // the current executable could not be resolved. They may also be set
+ // explicitly, bypassing the need to perform resolution.
+ Self string
+ SelfStat os.FileInfo
+
+ // PathDirs, if not zero, contains the list of directories to search. If
+ // zero, the os.PathListSeparator-delimited PATH environment variable will
+ // be used.
+ PathDirs []string
+}
+
+// ResolveSelf attempts to identify the current process. If successful, p's
+// Self will be set to an absolute path reference to Self, and its SelfStat
+// field will be set to the os.FileInfo for that path.
+//
+// If this process was invoked via symlink, the path to the symlink will be
+// returned if possible.
+func (p *Probe) ResolveSelf(argv0 string) error {
+ if p.Self != "" {
+ return nil
+ }
+
+ // Get the authoritative executable from the system.
+ exec, err := os.Executable()
+ if err != nil {
+ return errors.Annotate(err).Reason("failed to get executable").Err()
+ }
+
+ execStat, err := os.Stat(exec)
+ if err != nil {
+ return errors.Annotate(err).Reason("failed to stat executable: %(path)s").
+ D("path", exec).
+ Err()
+ }
+
+ // Before using "os.Executable" result, which is known to resolve symlinks on
+ // Linux, try and identify via argv0.
+ if argv0 != "" && filesystem.AbsPath(&argv0) == nil {
+ if st, err := os.Stat(argv0); err == nil && os.SameFile(execStat, st) {
+ // argv[0] is the same file as our executable, but may be an unresolved
+ // symlink. Prefer it.
+ p.Self, p.SelfStat = argv0, st
+ return nil
+ }
+ }
+
+ p.Self, p.SelfStat = exec, execStat
+ return nil
+}
+
+// Locate attempts to locate the system's Target by traversing the available
+// PATH.
+//
+// cached is the cached path, passed from wrapper to wrapper through the a
+// State struct in the environment. This may be empty, if there was no cached
+// path or if the cached path was invalid.
+//
+// env is the environment to operate with, and will not be modified during
+// execution.
+func (p *Probe) Locate(c context.Context, cached string, env environ.Env) (string, error) {
+ // If we have a cached path, check that it exists and is executable and use it
+ // if it is.
+ if cached != "" {
+ switch cachedStat, err := os.Stat(cached); {
+ case err == nil:
+ // Use the cached path. First, pass it through a sanity check to ensure
+ // that it is not self.
+ if p.SelfStat == nil || !os.SameFile(p.SelfStat, cachedStat) {
+ logging.Debugf(c, "Using cached value: %s", cached)
+ return cached, nil
+ }
+ logging.Debugf(c, "Cached value [%s] is this wrapper [%s]; ignoring.", cached, p.Self)
+
+ case os.IsNotExist(err):
+ // Our cached path doesn't exist, so we will have to look for a new one.
+
+ case err != nil:
+ // We couldn't check our cached path, so we will have to look for a new
+ // one. This is an unexpected error, though, so emit it.
+ logging.Debugf(c, "Failed to stat cached [%s]: %s", cached, err)
+ }
+ }
+
+ // Get stats on our parent directory. This may fail; if so, we'll skip the
+ // SameFile check.
+ var selfDir string
+ var selfDirStat os.FileInfo
+ if p.Self != "" {
+ selfDir = filepath.Dir(p.Self)
+
+ var err error
+ if selfDirStat, err = os.Stat(selfDir); err != nil {
+ logging.Debugf(c, "Failed to stat self directory [%s]: %s", selfDir, err)
+ }
+ }
+
+ // Walk through PATH. Our goal is to find the first program named Target that
+ // isn't self and doesn't identify as a wrapper.
+ pathDirs := p.PathDirs
+ if pathDirs == nil {
+ pathDirs = strings.Split(env.GetEmpty("PATH"), string(os.PathListSeparator))
+ }
+
+ // Build our list of directories to check for Git.
+ checkDirs := make([]string, 0, len(pathDirs)+len(p.RelativePathOverride))
+ if selfDir != "" {
+ for _, rpo := range p.RelativePathOverride {
+ checkDirs = append(checkDirs, filepath.Join(selfDir, filepath.FromSlash(rpo)))
+ }
+ }
+ checkDirs = append(checkDirs, pathDirs...)
+
+ // Iterate through each check directory and look for a Git candidate within
+ // it.
+ checked := make(map[string]struct{}, len(checkDirs))
+ for _, dir := range checkDirs {
+ if _, ok := checked[dir]; ok {
+ continue
+ }
+ checked[dir] = struct{}{}
+
+ path := p.checkDir(c, dir, selfDirStat, env)
+ if path != "" {
+ return path, nil
+ }
+ }
+
+ return "", errors.Reason("could not find target in system").
+ D("target", p.Target).
+ D("dirs", pathDirs).
+ Err()
+}
+
+// checkDir checks "checkDir" for our Target executable. It ignores
+// executables whose target is the same file or shares the same parent directory
+// as "self".
+func (p *Probe) checkDir(c context.Context, dir string, selfDir os.FileInfo, env environ.Env) string {
+ // If we have a self directory defined, ensure that "dir" isn't the same
+ // directory. If it is, we will ignore this option, since we are looking for
+ // something outside of the wrapper directory.
+ if selfDir != nil {
+ switch checkDirStat, err := os.Stat(dir); {
+ case err == nil:
+ // "dir" exists; if it is the same as "selfDir", we can ignore it.
+ if os.SameFile(selfDir, checkDirStat) {
+ logging.Debugf(c, "Candidate shares wrapper directory [%s]; skipping...", dir)
+ return ""
+ }
+
+ case os.IsNotExist(err):
+ logging.Debugf(c, "Candidate directory does not exist [%s]; skipping...", dir)
+ return ""
+
+ default:
+ logging.Debugf(c, "Failed to stat candidate directory [%s]: %s", dir, err)
+ return ""
+ }
+ }
+
+ t, err := findInDir(p.Target, dir, env)
+ if err != nil {
+ return ""
+ }
+
+ // Make sure this file isn't the same as "self", if available.
+ if p.SelfStat != nil {
+ switch st, err := os.Stat(t); {
+ case err == nil:
+ if os.SameFile(p.SelfStat, st) {
+ logging.Debugf(c, "Candidate [%s] is same file as wrapper; skipping...", t)
+ return ""
+ }
+
+ case os.IsNotExist(err):
+ // "t" no longer exists, so we can't use it.
+ return ""
+
+ default:
+ logging.Debugf(c, "Failed to stat candidate path [%s]: %s", t, err)
+ return ""
+ }
+ }
+
+ if err := filesystem.AbsPath(&t); err != nil {
+ logging.Debugf(c, "Failed to normalize candidate path [%s]: %s", t, err)
+ return ""
+ }
+
+ // Try running the candidate command and confirm that it is not a wrapper.
+ if p.CheckWrapper != nil {
+ switch isWrapper, err := p.CheckWrapper(c, t, env); {
+ case err != nil:
+ logging.Debugf(c, "Failed to check if [%s] is a wrapper: %s", t, err)
+ return ""
+
+ case isWrapper:
+ logging.Debugf(c, "Candidate is a wrapper: %s", t)
+ return ""
+ }
+ }
+
+ return t
+}
« no previous file with comments | « no previous file | common/system/prober/probe_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698