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

Side by Side Diff: common/wrapper/prober/probe.go

Issue 2932443002: Initial transfer. (Closed)
Patch Set: fix error 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/wrapper/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 both
iannucci 2017/06/07 17:01:12 "both" windows systems?
dnj 2017/06/07 17:09:12 Done.
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 is the absolute path to the current executable, resolved via
61 // ResolveSelf. It may be empty if the resolution has not been performed , or
62 // if the current executable could not be resolved.
63 //
64 // If Self is set, SelfStat must also be non-nil.
65 //
66 // Self may be set explicitly, or resolved via ResolveSelf.
67 Self string
68 // SelfStat is the FileInfo for self. If self is not empty, SelfStat wil l not
iannucci 2017/06/07 17:01:12 newline. If you want to group these, I would do
dnj 2017/06/07 17:09:12 Done.
69 // be nil.
70 //
71 // SelfStat may be set explicitly, or resolved via ResolveSelf.
72 SelfStat os.FileInfo
73
74 // PathDirs, if not zero, contains the list of directories to search. If
75 // zero, the os.PathListSeparator-delimited PATH environment variable wi ll
76 // be used.
77 PathDirs []string
78 }
79
80 // ResolveSelf attempts to identify the current process. If successful, p's
81 // Self will be set to an absolute path reference to Self, and its SelfStat
82 // field will be set to the os.FileInfo for that path.
83 //
84 // If this process was invoked via symlink, the path to the symlink will be
85 // returned if possible.
86 func (p *Probe) ResolveSelf(argv0 string) error {
87 if p.Self != "" {
88 return nil
89 }
90
91 // Get the authoritative executable from the system.
92 exec, err := os.Executable()
93 if err != nil {
94 return errors.Annotate(err).Reason("failed to get executable").E rr()
95 }
96
97 execStat, err := os.Stat(exec)
98 if err != nil {
99 return errors.Annotate(err).Reason("failed to stat executable: % (path)s").
100 D("path", exec).
101 Err()
102 }
103
104 // Before using "os.Executable" result, which is known to resolve symlin ks on
105 // Linux, try and identify via argv0.
106 if argv0 != "" && filesystem.AbsPath(&argv0) == nil {
107 if st, err := os.Stat(argv0); err == nil && os.SameFile(execStat , st) {
108 // argv[0] is the same file as our executable, but may b e an unresolved
109 // symlink. Prefer it.
110 p.Self, p.SelfStat = argv0, st
111 return nil
112 }
113 }
114
115 p.Self, p.SelfStat = exec, execStat
116 return nil
117 }
118
119 // Locate attempts to locate the system's Target by traversing the available
120 // PATH.
121 //
122 // cached is the cached path, passed from wrapper to wrapper through the a
123 // State struct in the environment. This may be empty, if there was no cached
124 // path or if the cached path was invalid.
125 //
126 // env is the environment to operate with, and will not be modified during
127 // execution.
128 func (p *Probe) Locate(c context.Context, cached string, env environ.Env) (strin g, error) {
129 // If we have a cached path, check that it exists and is executable and use it
130 // if it is.
131 if cached != "" {
132 switch cachedStat, err := os.Stat(cached); {
133 case err == nil:
134 // Use the cached path. First, pass it through a sanity check to ensure
135 // that it is not self.
136 if p.SelfStat == nil || !os.SameFile(p.SelfStat, cachedS tat) {
137 logging.Debugf(c, "Using cached value: %s", cach ed)
138 return cached, nil
139 }
140 logging.Debugf(c, "Cached value [%s] is this wrapper [%s ]; ignoring.", cached, p.Self)
141
142 case os.IsNotExist(err):
143 // Our cached path doesn't exist, so we will have to loo k for a new one.
144
145 case err != nil:
146 // We couldn't check our cached path, so we will have to look for a new
147 // one. This is an unexpected error, though, so emit it.
148 logging.Debugf(c, "Failed to stat cached [%s]: %s", cach ed, err)
149 }
150 }
151
152 // Get stats on our parent directory. This may fail; if so, we'll skip t he
153 // SameFile check.
154 var selfDir string
155 var selfDirStat os.FileInfo
156 if p.Self != "" {
157 selfDir = filepath.Dir(p.Self)
158
159 var err error
160 if selfDirStat, err = os.Stat(selfDir); err != nil {
161 logging.Debugf(c, "Failed to stat self directory [%s]: % s", selfDir, err)
162 }
163 }
164
165 // Walk through PATH. Our goal is to find the first program named Target that
166 // isn't self and doesn't identify as a wrapper.
167 pathDirs := p.PathDirs
168 if pathDirs == nil {
169 pathDirs = strings.Split(env.GetEmpty("PATH"), string(os.PathLis tSeparator))
170 }
171
172 // Build our list of directories to check for Git.
173 checkDirs := make([]string, 0, len(pathDirs)+len(p.RelativePathOverride) )
174 if selfDir != "" {
175 for _, rpo := range p.RelativePathOverride {
176 checkDirs = append(checkDirs, filepath.Join(selfDir, fil epath.FromSlash(rpo)))
177 }
178 }
179 checkDirs = append(checkDirs, pathDirs...)
180
181 // Iterate through each check directory and look for a Git candidate wit hin
182 // it.
183 checked := make(map[string]struct{}, len(checkDirs))
184 for _, dir := range checkDirs {
185 if _, ok := checked[dir]; ok {
186 continue
187 }
188 checked[dir] = struct{}{}
189
190 path := p.checkDir(c, dir, selfDirStat, env)
191 if path != "" {
192 return path, nil
193 }
194 }
195
196 return "", errors.Reason("could not find target in system").
197 D("target", p.Target).
198 D("dirs", pathDirs).
199 Err()
200 }
201
202 // checkDir checks "checkDir" for our Target executable. It ignores
203 // executables whose target is the same file or shares the same parent directory
204 // as "self".
205 func (p *Probe) checkDir(c context.Context, dir string, selfDir os.FileInfo, env environ.Env) string {
206 // If we have a self directory defined, ensure that "dir" isn't the same
207 // directory. If it is, we will ignore this option, since we are looking for
208 // something outside of the wrapper directory.
209 if selfDir != nil {
210 switch checkDirStat, err := os.Stat(dir); {
211 case err == nil:
212 // "dir" exists; if it is the same as "selfDir", we can ignore it.
213 if os.SameFile(selfDir, checkDirStat) {
214 logging.Debugf(c, "Candidate shares wrapper dire ctory [%s]; skipping...", dir)
215 return ""
216 }
217
218 case os.IsNotExist(err):
219 logging.Debugf(c, "Candidate directory does not exist [% s]; skipping...", dir)
220 return ""
221
222 default:
223 logging.Debugf(c, "Failed to stat candidate directory [% s]: %s", dir, err)
224 return ""
225 }
226 }
227
228 t, err := findInDir(p.Target, dir, env)
229 if err != nil {
230 return ""
231 }
232
233 // Make sure this file isn't the same as "self", if available.
234 if p.SelfStat != nil {
235 switch st, err := os.Stat(t); {
236 case err == nil:
237 if os.SameFile(p.SelfStat, st) {
238 logging.Debugf(c, "Candidate [%s] is same file a s wrapper; skipping...", t)
239 return ""
240 }
241
242 case os.IsNotExist(err):
243 // "t" no longer exists, so we can't use it.
244 return ""
245
246 default:
247 logging.Debugf(c, "Failed to stat candidate path [%s]: % s", t, err)
248 return ""
249 }
250 }
251
252 if err := filesystem.AbsPath(&t); err != nil {
253 logging.Debugf(c, "Failed to normalize candidate path [%s]: %s", t, err)
254 return ""
255 }
256
257 // Try running the candidate command and confirm that it is not a wrappe r.
258 if p.CheckWrapper != nil {
259 switch isWrapper, err := p.CheckWrapper(c, t, env); {
260 case err != nil:
261 logging.Debugf(c, "Failed to check if [%s] is a wrapper: %s", t, err)
262 return ""
263
264 case isWrapper:
265 logging.Debugf(c, "Candidate is a wrapper: %s", t)
266 return ""
267 }
268 }
269
270 return t
271 }
OLDNEW
« no previous file with comments | « no previous file | common/wrapper/prober/probe_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698