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 main
16
17import (
18	"bytes"
19	"fmt"
20	"io"
21	"io/ioutil"
22	"os"
23	"os/exec"
24	"path/filepath"
25	"strconv"
26	"syscall"
27
28	"android/soong/ui/build/paths"
29)
30
31func main() {
32	interposer, err := os.Executable()
33	if err != nil {
34		fmt.Fprintln(os.Stderr, "Unable to locate interposer executable:", err)
35		os.Exit(1)
36	}
37
38	if fi, err := os.Lstat(interposer); err == nil {
39		if fi.Mode()&os.ModeSymlink != 0 {
40			link, err := os.Readlink(interposer)
41			if err != nil {
42				fmt.Fprintln(os.Stderr, "Unable to read link to interposer executable:", err)
43				os.Exit(1)
44			}
45			if filepath.IsAbs(link) {
46				interposer = link
47			} else {
48				interposer = filepath.Join(filepath.Dir(interposer), link)
49			}
50		}
51	} else {
52		fmt.Fprintln(os.Stderr, "Unable to stat interposer executable:", err)
53		os.Exit(1)
54	}
55
56	exitCode, err := Main(os.Stdout, os.Stderr, interposer, os.Args, mainOpts{
57		sendLog:       paths.SendLog,
58		config:        paths.GetConfig,
59		lookupParents: lookupParents,
60	})
61	if err != nil {
62		fmt.Fprintln(os.Stderr, err.Error())
63	}
64	os.Exit(exitCode)
65}
66
67var usage = fmt.Errorf(`To use the PATH interposer:
68 * Write the original PATH variable to <interposer>_origpath
69 * Set up a directory of symlinks to the PATH interposer, and use that in PATH
70
71If a tool isn't in the allowed list, a log will be posted to the unix domain
72socket at <interposer>_log.`)
73
74type mainOpts struct {
75	sendLog       func(logSocket string, entry *paths.LogEntry, done chan interface{})
76	config        func(name string) paths.PathConfig
77	lookupParents func() []paths.LogProcess
78}
79
80func Main(stdout, stderr io.Writer, interposer string, args []string, opts mainOpts) (int, error) {
81	base := filepath.Base(args[0])
82
83	origPathFile := interposer + "_origpath"
84	if base == filepath.Base(interposer) {
85		return 1, usage
86	}
87
88	origPath, err := ioutil.ReadFile(origPathFile)
89	if err != nil {
90		if os.IsNotExist(err) {
91			return 1, usage
92		} else {
93			return 1, fmt.Errorf("Failed to read original PATH: %v", err)
94		}
95	}
96
97	cmd := &exec.Cmd{
98		Args: args,
99		Env:  os.Environ(),
100
101		Stdin:  os.Stdin,
102		Stdout: stdout,
103		Stderr: stderr,
104	}
105
106	if err := os.Setenv("PATH", string(origPath)); err != nil {
107		return 1, fmt.Errorf("Failed to set PATH env: %v", err)
108	}
109
110	if config := opts.config(base); config.Log || config.Error {
111		var procs []paths.LogProcess
112		if opts.lookupParents != nil {
113			procs = opts.lookupParents()
114		}
115
116		if opts.sendLog != nil {
117			waitForLog := make(chan interface{})
118			opts.sendLog(interposer+"_log", &paths.LogEntry{
119				Basename: base,
120				Args:     args,
121				Parents:  procs,
122			}, waitForLog)
123			defer func() { <-waitForLog }()
124		}
125		if config.Error {
126			return 1, fmt.Errorf("%q is not allowed to be used. See https://android.googlesource.com/platform/build/+/master/Changes.md#PATH_Tools for more information.", base)
127		}
128	}
129
130	cmd.Path, err = exec.LookPath(base)
131	if err != nil {
132		return 1, err
133	}
134
135	if err = cmd.Run(); err != nil {
136		if exitErr, ok := err.(*exec.ExitError); ok {
137			if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
138				if status.Exited() {
139					return status.ExitStatus(), nil
140				} else if status.Signaled() {
141					exitCode := 128 + int(status.Signal())
142					return exitCode, nil
143				} else {
144					return 1, exitErr
145				}
146			} else {
147				return 1, nil
148			}
149		}
150	}
151
152	return 0, nil
153}
154
155type procEntry struct {
156	Pid     int
157	Ppid    int
158	Command string
159}
160
161func readProcs() map[int]procEntry {
162	cmd := exec.Command("ps", "-o", "pid,ppid,command")
163	data, err := cmd.Output()
164	if err != nil {
165		return nil
166	}
167
168	return parseProcs(data)
169}
170
171func parseProcs(data []byte) map[int]procEntry {
172	lines := bytes.Split(data, []byte("\n"))
173	if len(lines) < 2 {
174		return nil
175	}
176	// Remove the header
177	lines = lines[1:]
178
179	ret := make(map[int]procEntry, len(lines))
180	for _, line := range lines {
181		fields := bytes.SplitN(line, []byte(" "), 2)
182		if len(fields) != 2 {
183			continue
184		}
185
186		pid, err := strconv.Atoi(string(fields[0]))
187		if err != nil {
188			continue
189		}
190
191		line = bytes.TrimLeft(fields[1], " ")
192
193		fields = bytes.SplitN(line, []byte(" "), 2)
194		if len(fields) != 2 {
195			continue
196		}
197
198		ppid, err := strconv.Atoi(string(fields[0]))
199		if err != nil {
200			continue
201		}
202
203		ret[pid] = procEntry{
204			Pid:     pid,
205			Ppid:    ppid,
206			Command: string(bytes.TrimLeft(fields[1], " ")),
207		}
208	}
209
210	return ret
211}
212
213func lookupParents() []paths.LogProcess {
214	procs := readProcs()
215	if procs == nil {
216		return nil
217	}
218
219	list := []paths.LogProcess{}
220	pid := os.Getpid()
221	for {
222		entry, ok := procs[pid]
223		if !ok {
224			break
225		}
226
227		list = append([]paths.LogProcess{
228			{
229				Pid:     pid,
230				Command: entry.Command,
231			},
232		}, list...)
233
234		pid = entry.Ppid
235	}
236
237	return list
238}
239