1// Copyright 2018 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package build
16
17import (
18	"fmt"
19	"io/ioutil"
20	"os"
21	"os/exec"
22	"path/filepath"
23	"runtime"
24	"strings"
25
26	"github.com/google/blueprint/microfactory"
27
28	"android/soong/ui/build/paths"
29	"android/soong/ui/metrics"
30)
31
32func parsePathDir(dir string) []string {
33	f, err := os.Open(dir)
34	if err != nil {
35		return nil
36	}
37	defer f.Close()
38
39	if s, err := f.Stat(); err != nil || !s.IsDir() {
40		return nil
41	}
42
43	infos, err := f.Readdir(-1)
44	if err != nil {
45		return nil
46	}
47
48	ret := make([]string, 0, len(infos))
49	for _, info := range infos {
50		if m := info.Mode(); !m.IsDir() && m&0111 != 0 {
51			ret = append(ret, info.Name())
52		}
53	}
54	return ret
55}
56
57// A "lite" version of SetupPath used for dumpvars, or other places that need
58// minimal overhead (but at the expense of logging). If tmpDir is empty, the
59// default TMPDIR is used from config.
60func SetupLitePath(ctx Context, config Config, tmpDir string) {
61	if config.pathReplaced {
62		return
63	}
64
65	ctx.BeginTrace(metrics.RunSetupTool, "litepath")
66	defer ctx.EndTrace()
67
68	origPath, _ := config.Environment().Get("PATH")
69
70	if tmpDir == "" {
71		tmpDir, _ = config.Environment().Get("TMPDIR")
72	}
73	myPath := filepath.Join(tmpDir, "path")
74	ensureEmptyDirectoriesExist(ctx, myPath)
75
76	os.Setenv("PATH", origPath)
77	for name, pathConfig := range paths.Configuration {
78		if !pathConfig.Symlink {
79			continue
80		}
81
82		origExec, err := exec.LookPath(name)
83		if err != nil {
84			continue
85		}
86		origExec, err = filepath.Abs(origExec)
87		if err != nil {
88			continue
89		}
90
91		err = os.Symlink(origExec, filepath.Join(myPath, name))
92		if err != nil {
93			ctx.Fatalln("Failed to create symlink:", err)
94		}
95	}
96
97	myPath, _ = filepath.Abs(myPath)
98
99	prebuiltsPath, _ := filepath.Abs("prebuilts/build-tools/path/" + runtime.GOOS + "-x86")
100	myPath = prebuiltsPath + string(os.PathListSeparator) + myPath
101
102	config.Environment().Set("PATH", myPath)
103	config.pathReplaced = true
104}
105
106func SetupPath(ctx Context, config Config) {
107	if config.pathReplaced {
108		return
109	}
110
111	ctx.BeginTrace(metrics.RunSetupTool, "path")
112	defer ctx.EndTrace()
113
114	origPath, _ := config.Environment().Get("PATH")
115	myPath := filepath.Join(config.OutDir(), ".path")
116	interposer := myPath + "_interposer"
117
118	var cfg microfactory.Config
119	cfg.Map("android/soong", "build/soong")
120	cfg.TrimPath, _ = filepath.Abs(".")
121	if _, err := microfactory.Build(&cfg, interposer, "android/soong/cmd/path_interposer"); err != nil {
122		ctx.Fatalln("Failed to build path interposer:", err)
123	}
124
125	if err := ioutil.WriteFile(interposer+"_origpath", []byte(origPath), 0777); err != nil {
126		ctx.Fatalln("Failed to write original path:", err)
127	}
128
129	entries, err := paths.LogListener(ctx.Context, interposer+"_log")
130	if err != nil {
131		ctx.Fatalln("Failed to listen for path logs:", err)
132	}
133
134	go func() {
135		for log := range entries {
136			curPid := os.Getpid()
137			for i, proc := range log.Parents {
138				if proc.Pid == curPid {
139					log.Parents = log.Parents[i:]
140					break
141				}
142			}
143			procPrints := []string{
144				"See https://android.googlesource.com/platform/build/+/master/Changes.md#PATH_Tools for more information.",
145			}
146			if len(log.Parents) > 0 {
147				procPrints = append(procPrints, "Process tree:")
148				for i, proc := range log.Parents {
149					procPrints = append(procPrints, fmt.Sprintf("%s→ %s", strings.Repeat(" ", i), proc.Command))
150				}
151			}
152
153			config := paths.GetConfig(log.Basename)
154			if config.Error {
155				ctx.Printf("Disallowed PATH tool %q used: %#v", log.Basename, log.Args)
156				for _, line := range procPrints {
157					ctx.Println(line)
158				}
159			} else {
160				ctx.Verbosef("Unknown PATH tool %q used: %#v", log.Basename, log.Args)
161				for _, line := range procPrints {
162					ctx.Verboseln(line)
163				}
164			}
165		}
166	}()
167
168	ensureEmptyDirectoriesExist(ctx, myPath)
169
170	var execs []string
171	for _, pathEntry := range filepath.SplitList(origPath) {
172		if pathEntry == "" {
173			// Ignore the current directory
174			continue
175		}
176		// TODO(dwillemsen): remove path entries under TOP? or anything
177		// that looks like an android source dir? They won't exist on
178		// the build servers, since they're added by envsetup.sh.
179		// (Except for the JDK, which is configured in ui/build/config.go)
180
181		execs = append(execs, parsePathDir(pathEntry)...)
182	}
183
184	if config.Environment().IsEnvTrue("TEMPORARY_DISABLE_PATH_RESTRICTIONS") {
185		ctx.Fatalln("TEMPORARY_DISABLE_PATH_RESTRICTIONS was a temporary migration method, and is now obsolete.")
186	}
187
188	for _, name := range execs {
189		if !paths.GetConfig(name).Symlink {
190			continue
191		}
192
193		err := os.Symlink("../.path_interposer", filepath.Join(myPath, name))
194		// Intentionally ignore existing files -- that means that we
195		// just created it, and the first one should win.
196		if err != nil && !os.IsExist(err) {
197			ctx.Fatalln("Failed to create symlink:", err)
198		}
199	}
200
201	myPath, _ = filepath.Abs(myPath)
202
203	// We put some prebuilts in $PATH, since it's infeasible to add dependencies for all of
204	// them.
205	prebuiltsPath, _ := filepath.Abs("prebuilts/build-tools/path/" + runtime.GOOS + "-x86")
206	myPath = prebuiltsPath + string(os.PathListSeparator) + myPath
207
208	config.Environment().Set("PATH", myPath)
209	config.pathReplaced = true
210}
211