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

Side by Side 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 unified diff | Download patch
« no previous file with comments | « no previous file | common/system/prober/probe_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2017 The LUCI Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // Package prober exports Probe, which implements logic to identify a wrapper's
6 // wrapped target. In addition to basic PATH/filename lookup, Prober contains
7 // logic to ensure that the wrapper is not the same software as the current
8 // running instance, and enables optional hard-coded wrap target paths and
9 // runtime checks.
10 package prober
11
12 import (
13 "os"
14 "path/filepath"
15 "strings"
16
17 "golang.org/x/net/context"
18
19 "github.com/luci/luci-go/common/errors"
20 "github.com/luci/luci-go/common/logging"
21 "github.com/luci/luci-go/common/system/environ"
22 "github.com/luci/luci-go/common/system/filesystem"
23 )
24
25 // CheckWrapperFunc is an optional function that can be implemented for a
26 // Prober to check if a candidate path is a wrapper.
27 type CheckWrapperFunc func(c context.Context, path string, env environ.Env) (isW rapper bool, err error)
28
29 // Probe can Locate a Target executable by probing the local system PATH.
30 //
31 // Target should be an executable name resolvable by exec.LookPath. On
32 // Windows systems, this may omit the executable extension (e.g., "bat", "exe")
33 // since that is augmented via the PATHEXT environment variable (see
34 // "probe_windows.go").
35 type Probe struct {
36 // Target is the name of the target (as seen by exec.LookPath) that we a re
37 // searching for.
38 Target string
39
40 // RelativePathOverride is a series of forward-slash-delimited paths to
41 // directories relative to the wrapper executable that will be checked
42 // prior to checking PATH. This allows bundles (e.g., CIPD) that include both
43 // the wrapper and a real implementation, to force the wrapper to use
44 // the bundled implementation.
45 RelativePathOverride []string
46
47 // CheckWrapper, if not nil, is a function called on a candidate wrapper to
48 // determine whether or not that candidate is valid.
49 //
50 // On success, it will return isWrapper, which will be true if path is a
51 // wrapper instance and false if it is not. If an error occurred during
52 // checking, the error should be returned and isWrapper will be ignored. If
53 // a candidate is a wrapper, or if an error occurred during check, the
54 // candidate will be discarded and the probe will continue.
55 //
56 // CheckWrapper should be lightweight and fast, as it may be called mult iple
57 // times.
58 CheckWrapper CheckWrapperFunc
59
60 // Self and Selfstat are resolved contain the path and FileInfo of the
61 // currently running executable, respectively. They can both be resolved via
62 // ResolveSelf, and may be empty if resolution has not been performed, o r if
63 // the current executable could not be resolved. They may also be set
64 // explicitly, bypassing the need to perform resolution.
65 Self string
66 SelfStat os.FileInfo
67
68 // PathDirs, if not zero, contains the list of directories to search. If
69 // zero, the os.PathListSeparator-delimited PATH environment variable wi ll
70 // be used.
71 PathDirs []string
72 }
73
74 // ResolveSelf attempts to identify the current process. If successful, p's
75 // Self will be set to an absolute path reference to Self, and its SelfStat
76 // field will be set to the os.FileInfo for that path.
77 //
78 // If this process was invoked via symlink, the path to the symlink will be
79 // returned if possible.
80 func (p *Probe) ResolveSelf(argv0 string) error {
81 if p.Self != "" {
82 return nil
83 }
84
85 // Get the authoritative executable from the system.
86 exec, err := os.Executable()
87 if err != nil {
88 return errors.Annotate(err).Reason("failed to get executable").E rr()
89 }
90
91 execStat, err := os.Stat(exec)
92 if err != nil {
93 return errors.Annotate(err).Reason("failed to stat executable: % (path)s").
94 D("path", exec).
95 Err()
96 }
97
98 // Before using "os.Executable" result, which is known to resolve symlin ks on
99 // Linux, try and identify via argv0.
100 if argv0 != "" && filesystem.AbsPath(&argv0) == nil {
101 if st, err := os.Stat(argv0); err == nil && os.SameFile(execStat , st) {
102 // argv[0] is the same file as our executable, but may b e an unresolved
103 // symlink. Prefer it.
104 p.Self, p.SelfStat = argv0, st
105 return nil
106 }
107 }
108
109 p.Self, p.SelfStat = exec, execStat
110 return nil
111 }
112
113 // Locate attempts to locate the system's Target by traversing the available
114 // PATH.
115 //
116 // cached is the cached path, passed from wrapper to wrapper through the a
117 // State struct in the environment. This may be empty, if there was no cached
118 // path or if the cached path was invalid.
119 //
120 // env is the environment to operate with, and will not be modified during
121 // execution.
122 func (p *Probe) Locate(c context.Context, cached string, env environ.Env) (strin g, error) {
123 // If we have a cached path, check that it exists and is executable and use it
124 // if it is.
125 if cached != "" {
126 switch cachedStat, err := os.Stat(cached); {
127 case err == nil:
128 // Use the cached path. First, pass it through a sanity check to ensure
129 // that it is not self.
130 if p.SelfStat == nil || !os.SameFile(p.SelfStat, cachedS tat) {
131 logging.Debugf(c, "Using cached value: %s", cach ed)
132 return cached, nil
133 }
134 logging.Debugf(c, "Cached value [%s] is this wrapper [%s ]; ignoring.", cached, p.Self)
135
136 case os.IsNotExist(err):
137 // Our cached path doesn't exist, so we will have to loo k for a new one.
138
139 case err != nil:
140 // We couldn't check our cached path, so we will have to look for a new
141 // one. This is an unexpected error, though, so emit it.
142 logging.Debugf(c, "Failed to stat cached [%s]: %s", cach ed, err)
143 }
144 }
145
146 // Get stats on our parent directory. This may fail; if so, we'll skip t he
147 // SameFile check.
148 var selfDir string
149 var selfDirStat os.FileInfo
150 if p.Self != "" {
151 selfDir = filepath.Dir(p.Self)
152
153 var err error
154 if selfDirStat, err = os.Stat(selfDir); err != nil {
155 logging.Debugf(c, "Failed to stat self directory [%s]: % s", selfDir, err)
156 }
157 }
158
159 // Walk through PATH. Our goal is to find the first program named Target that
160 // isn't self and doesn't identify as a wrapper.
161 pathDirs := p.PathDirs
162 if pathDirs == nil {
163 pathDirs = strings.Split(env.GetEmpty("PATH"), string(os.PathLis tSeparator))
164 }
165
166 // Build our list of directories to check for Git.
167 checkDirs := make([]string, 0, len(pathDirs)+len(p.RelativePathOverride) )
168 if selfDir != "" {
169 for _, rpo := range p.RelativePathOverride {
170 checkDirs = append(checkDirs, filepath.Join(selfDir, fil epath.FromSlash(rpo)))
171 }
172 }
173 checkDirs = append(checkDirs, pathDirs...)
174
175 // Iterate through each check directory and look for a Git candidate wit hin
176 // it.
177 checked := make(map[string]struct{}, len(checkDirs))
178 for _, dir := range checkDirs {
179 if _, ok := checked[dir]; ok {
180 continue
181 }
182 checked[dir] = struct{}{}
183
184 path := p.checkDir(c, dir, selfDirStat, env)
185 if path != "" {
186 return path, nil
187 }
188 }
189
190 return "", errors.Reason("could not find target in system").
191 D("target", p.Target).
192 D("dirs", pathDirs).
193 Err()
194 }
195
196 // checkDir checks "checkDir" for our Target executable. It ignores
197 // executables whose target is the same file or shares the same parent directory
198 // as "self".
199 func (p *Probe) checkDir(c context.Context, dir string, selfDir os.FileInfo, env environ.Env) string {
200 // If we have a self directory defined, ensure that "dir" isn't the same
201 // directory. If it is, we will ignore this option, since we are looking for
202 // something outside of the wrapper directory.
203 if selfDir != nil {
204 switch checkDirStat, err := os.Stat(dir); {
205 case err == nil:
206 // "dir" exists; if it is the same as "selfDir", we can ignore it.
207 if os.SameFile(selfDir, checkDirStat) {
208 logging.Debugf(c, "Candidate shares wrapper dire ctory [%s]; skipping...", dir)
209 return ""
210 }
211
212 case os.IsNotExist(err):
213 logging.Debugf(c, "Candidate directory does not exist [% s]; skipping...", dir)
214 return ""
215
216 default:
217 logging.Debugf(c, "Failed to stat candidate directory [% s]: %s", dir, err)
218 return ""
219 }
220 }
221
222 t, err := findInDir(p.Target, dir, env)
223 if err != nil {
224 return ""
225 }
226
227 // Make sure this file isn't the same as "self", if available.
228 if p.SelfStat != nil {
229 switch st, err := os.Stat(t); {
230 case err == nil:
231 if os.SameFile(p.SelfStat, st) {
232 logging.Debugf(c, "Candidate [%s] is same file a s wrapper; skipping...", t)
233 return ""
234 }
235
236 case os.IsNotExist(err):
237 // "t" no longer exists, so we can't use it.
238 return ""
239
240 default:
241 logging.Debugf(c, "Failed to stat candidate path [%s]: % s", t, err)
242 return ""
243 }
244 }
245
246 if err := filesystem.AbsPath(&t); err != nil {
247 logging.Debugf(c, "Failed to normalize candidate path [%s]: %s", t, err)
248 return ""
249 }
250
251 // Try running the candidate command and confirm that it is not a wrappe r.
252 if p.CheckWrapper != nil {
253 switch isWrapper, err := p.CheckWrapper(c, t, env); {
254 case err != nil:
255 logging.Debugf(c, "Failed to check if [%s] is a wrapper: %s", t, err)
256 return ""
257
258 case isWrapper:
259 logging.Debugf(c, "Candidate is a wrapper: %s", t)
260 return ""
261 }
262 }
263
264 return t
265 }
OLDNEW
« 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