// Copyright 2018 Google Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "bytes" "fmt" "io" "io/ioutil" "os" "os/exec" "path/filepath" "strconv" "syscall" "android/soong/ui/build/paths" ) func main() { interposer, err := os.Executable() if err != nil { fmt.Fprintln(os.Stderr, "Unable to locate interposer executable:", err) os.Exit(1) } if fi, err := os.Lstat(interposer); err == nil { if fi.Mode()&os.ModeSymlink != 0 { link, err := os.Readlink(interposer) if err != nil { fmt.Fprintln(os.Stderr, "Unable to read link to interposer executable:", err) os.Exit(1) } if filepath.IsAbs(link) { interposer = link } else { interposer = filepath.Join(filepath.Dir(interposer), link) } } } else { fmt.Fprintln(os.Stderr, "Unable to stat interposer executable:", err) os.Exit(1) } disableError := false if e, ok := os.LookupEnv("TEMPORARY_DISABLE_PATH_RESTRICTIONS"); ok { disableError = e == "1" || e == "y" || e == "yes" || e == "on" || e == "true" } exitCode, err := Main(os.Stdout, os.Stderr, interposer, os.Args, mainOpts{ disableError: disableError, sendLog: paths.SendLog, config: paths.GetConfig, lookupParents: lookupParents, }) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) } os.Exit(exitCode) } var usage = fmt.Errorf(`To use the PATH interposer: * Write the original PATH variable to _origpath * Set up a directory of symlinks to the PATH interposer, and use that in PATH If a tool isn't in the allowed list, a log will be posted to the unix domain socket at _log.`) type mainOpts struct { disableError bool sendLog func(logSocket string, entry *paths.LogEntry, done chan interface{}) config func(name string) paths.PathConfig lookupParents func() []paths.LogProcess } func Main(stdout, stderr io.Writer, interposer string, args []string, opts mainOpts) (int, error) { base := filepath.Base(args[0]) origPathFile := interposer + "_origpath" if base == filepath.Base(interposer) { return 1, usage } origPath, err := ioutil.ReadFile(origPathFile) if err != nil { if os.IsNotExist(err) { return 1, usage } else { return 1, fmt.Errorf("Failed to read original PATH: %v", err) } } cmd := &exec.Cmd{ Args: args, Env: os.Environ(), Stdin: os.Stdin, Stdout: stdout, Stderr: stderr, } if err := os.Setenv("PATH", string(origPath)); err != nil { return 1, fmt.Errorf("Failed to set PATH env: %v", err) } if config := opts.config(base); config.Log || config.Error { var procs []paths.LogProcess if opts.lookupParents != nil { procs = opts.lookupParents() } if opts.sendLog != nil { waitForLog := make(chan interface{}) opts.sendLog(interposer+"_log", &paths.LogEntry{ Basename: base, Args: args, Parents: procs, }, waitForLog) defer func() { <-waitForLog }() } if config.Error && !opts.disableError { 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) } } cmd.Path, err = exec.LookPath(base) if err != nil { return 1, err } if err = cmd.Run(); err != nil { if exitErr, ok := err.(*exec.ExitError); ok { if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { if status.Exited() { return status.ExitStatus(), nil } else if status.Signaled() { exitCode := 128 + int(status.Signal()) return exitCode, nil } else { return 1, exitErr } } else { return 1, nil } } } return 0, nil } type procEntry struct { Pid int Ppid int Command string } func readProcs() map[int]procEntry { cmd := exec.Command("ps", "-o", "pid,ppid,command") data, err := cmd.Output() if err != nil { return nil } return parseProcs(data) } func parseProcs(data []byte) map[int]procEntry { lines := bytes.Split(data, []byte("\n")) if len(lines) < 2 { return nil } // Remove the header lines = lines[1:] ret := make(map[int]procEntry, len(lines)) for _, line := range lines { fields := bytes.SplitN(line, []byte(" "), 2) if len(fields) != 2 { continue } pid, err := strconv.Atoi(string(fields[0])) if err != nil { continue } line = bytes.TrimLeft(fields[1], " ") fields = bytes.SplitN(line, []byte(" "), 2) if len(fields) != 2 { continue } ppid, err := strconv.Atoi(string(fields[0])) if err != nil { continue } ret[pid] = procEntry{ Pid: pid, Ppid: ppid, Command: string(bytes.TrimLeft(fields[1], " ")), } } return ret } func lookupParents() []paths.LogProcess { procs := readProcs() if procs == nil { return nil } list := []paths.LogProcess{} pid := os.Getpid() for { entry, ok := procs[pid] if !ok { break } list = append([]paths.LogProcess{ { Pid: pid, Command: entry.Command, }, }, list...) pid = entry.Ppid } return list }