platform_build_soong/cmd/path_interposer/main.go

248 lines
5.4 KiB
Go
Raw Normal View History

// 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 <interposer>_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 <interposer>_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
}