Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 09f4540d authored by Dan Willemsen's avatar Dan Willemsen
Browse files

Revert "Revert "Revert "Add path interposer"""

This reverts commit c59a92cb.

Reason for revert: tests are broken with long OUT_DIRs
They're directly calling net.Listen, and not using the fallback
for long socket names.

Change-Id: Id14cbd499fd9b36c6926b7552d3554340cb0916c
parent c59a92cb
Loading
Loading
Loading
Loading

cmd/path_interposer/Android.bp

deleted100644 → 0
+0 −20
Original line number Diff line number Diff line
// 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.

blueprint_go_binary {
    name: "path_interposer",
    deps: ["soong-ui-build-paths"],
    srcs: ["main.go"],
    testSrcs: ["main_test.go"],
}

cmd/path_interposer/main.go

deleted100644 → 0
+0 −247
Original line number Diff line number Diff line
// 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
}

cmd/path_interposer/main_test.go

deleted100644 → 0
+0 −196
Original line number Diff line number Diff line
// 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 (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"testing"

	"android/soong/ui/build/paths"
)

var tmpDir string
var origPATH string

func TestMain(m *testing.M) {
	os.Exit(func() int {
		var err error
		tmpDir, err = ioutil.TempDir("", "interposer_test")
		if err != nil {
			panic(err)
		}
		defer os.RemoveAll(tmpDir)

		origPATH = os.Getenv("PATH")
		err = os.Setenv("PATH", "")
		if err != nil {
			panic(err)
		}

		return m.Run()
	}())
}

func setup(t *testing.T) string {
	f, err := ioutil.TempFile(tmpDir, "interposer")
	if err != nil {
		t.Fatal(err)
	}
	defer f.Close()

	err = ioutil.WriteFile(f.Name()+"_origpath", []byte(origPATH), 0666)
	if err != nil {
		t.Fatal(err)
	}
	return f.Name()
}

func TestInterposer(t *testing.T) {
	interposer := setup(t)

	logConfig := func(name string) paths.PathConfig {
		if name == "true" {
			return paths.PathConfig{
				Log:   false,
				Error: false,
			}
		} else if name == "path_interposer_test_not_allowed" {
			return paths.PathConfig{
				Log:   false,
				Error: true,
			}
		}
		return paths.PathConfig{
			Log:   true,
			Error: false,
		}
	}

	testCases := []struct {
		name string
		args []string

		exitCode int
		err      error
		logEntry string
	}{
		{
			name: "direct call",
			args: []string{interposer},

			exitCode: 1,
			err:      usage,
		},
		{
			name: "relative call",
			args: []string{filepath.Base(interposer)},

			exitCode: 1,
			err:      usage,
		},
		{
			name: "true",
			args: []string{"/my/path/true"},
		},
		{
			name: "relative true",
			args: []string{"true"},
		},
		{
			name: "exit code",
			args: []string{"bash", "-c", "exit 42"},

			exitCode: 42,
			logEntry: "bash",
		},
		{
			name: "signal",
			args: []string{"bash", "-c", "kill -9 $$"},

			exitCode: 137,
			logEntry: "bash",
		},
		{
			name: "does not exist",
			args: []string{"path_interposer_test_does_not_exist"},

			exitCode: 1,
			err:      fmt.Errorf(`exec: "path_interposer_test_does_not_exist": executable file not found in $PATH`),
			logEntry: "path_interposer_test_does_not_exist",
		},
		{
			name: "not allowed",
			args: []string{"path_interposer_test_not_allowed"},

			exitCode: 1,
			err:      fmt.Errorf(`"path_interposer_test_not_allowed" is not allowed to be used. See https://android.googlesource.com/platform/build/+/master/Changes.md#PATH_Tools for more information.`),
			logEntry: "path_interposer_test_not_allowed",
		},
	}

	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			logged := false
			logFunc := func(logSocket string, entry *paths.LogEntry, done chan interface{}) {
				defer close(done)

				logged = true
				if entry.Basename != testCase.logEntry {
					t.Errorf("unexpected log entry:\nwant: %q\n got: %q", testCase.logEntry, entry.Basename)
				}
			}

			exitCode, err := Main(ioutil.Discard, ioutil.Discard, interposer, testCase.args, mainOpts{
				sendLog: logFunc,
				config:  logConfig,
			})

			errstr := func(err error) string {
				if err == nil {
					return ""
				}
				return err.Error()
			}
			if errstr(testCase.err) != errstr(err) {
				t.Errorf("unexpected error:\nwant: %v\n got: %v", testCase.err, err)
			}
			if testCase.exitCode != exitCode {
				t.Errorf("expected exit code %d, got %d", testCase.exitCode, exitCode)
			}
			if !logged && testCase.logEntry != "" {
				t.Errorf("no log entry, but expected %q", testCase.logEntry)
			}
		})
	}
}

func TestMissingPath(t *testing.T) {
	interposer := setup(t)
	err := os.Remove(interposer + "_origpath")
	if err != nil {
		t.Fatalf("Failed to remove:", err)
	}

	exitCode, err := Main(ioutil.Discard, ioutil.Discard, interposer, []string{"true"}, mainOpts{})
	if err != usage {
		t.Errorf("Unexpected error:\n got: %v\nwant: %v", err, usage)
	}
	if exitCode != 1 {
		t.Errorf("expected exit code %d, got %d", 1, exitCode)
	}
}
+2 −0
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ def SearchPathEnv(name):
  for directory in search_path:
    if directory == '': continue
    path = os.path.join(directory, name)
    if os.path.islink(path):
      path = os.path.realpath(path)
    # Check if path is actual executable file.
    if os.path.isfile(path) and os.access(path, os.X_OK):
      return path
+0 −14
Original line number Diff line number Diff line
@@ -12,23 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.

bootstrap_go_package {
    name: "soong-ui-build-paths",
    pkgPath: "android/soong/ui/build/paths",
    srcs: [
        "paths/config.go",
        "paths/logs.go",
    ],
    testSrcs: [
        "paths/logs_test.go",
    ],
}

bootstrap_go_package {
    name: "soong-ui-build",
    pkgPath: "android/soong/ui/build",
    deps: [
        "soong-ui-build-paths",
        "soong-ui-logger",
        "soong-ui-tracer",
        "soong-shared",
@@ -46,7 +33,6 @@ bootstrap_go_package {
        "finder.go",
        "kati.go",
        "ninja.go",
        "path.go",
        "proc_sync.go",
        "signal.go",
        "soong.go",
Loading