From bd4ef25a251ec1c052d06e5f7e3579843216e828 Mon Sep 17 00:00:00 2001 From: Andrew Woloszyn Date: Tue, 22 May 2018 11:18:37 -0400 Subject: [PATCH 01/10] Added device_info executable. This will print the device information on stdout, as either json or a binary protobuf. --- BUILD.bazel | 1 + core/os/device/deviceinfo/cc/exe/BUILD.bazel | 27 +++++++ core/os/device/deviceinfo/cc/exe/main.cpp | 76 ++++++++++++++++++++ 3 files changed, 104 insertions(+) create mode 100644 core/os/device/deviceinfo/cc/exe/BUILD.bazel create mode 100644 core/os/device/deviceinfo/cc/exe/main.cpp diff --git a/BUILD.bazel b/BUILD.bazel index e5917950c3..676e44ae1a 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -62,6 +62,7 @@ copy_to( "//cmd/gapis", "//cmd/gapit", "//tools/build:build.properties", + "//core/os/device/deviceinfo/cc/exe:device_info", ] + select({ "//tools/build:no-android": [], "//conditions:default": [ diff --git a/core/os/device/deviceinfo/cc/exe/BUILD.bazel b/core/os/device/deviceinfo/cc/exe/BUILD.bazel new file mode 100644 index 0000000000..ec31ab2baa --- /dev/null +++ b/core/os/device/deviceinfo/cc/exe/BUILD.bazel @@ -0,0 +1,27 @@ +# Copyright (C) 2018 Google Inc. +# +# 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. + +load("//tools/build:rules.bzl", "cc_copts", "cc_stripped_binary", "android_dynamic_library", "android_native_app_glue") + +cc_stripped_binary( + name = "device_info", + srcs = ["main.cpp"], + copts = cc_copts(), + visibility = ["//visibility:public"], + deps = [ + "//core/os/device/deviceinfo/cc:cc", + "@com_google_protobuf//:protobuf", + "//core/os/device:device_cc_proto", + ], +) diff --git a/core/os/device/deviceinfo/cc/exe/main.cpp b/core/os/device/deviceinfo/cc/exe/main.cpp new file mode 100644 index 0000000000..594640df2f --- /dev/null +++ b/core/os/device/deviceinfo/cc/exe/main.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2018 Google Inc. + * + * 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. + */ + +#include +#include +#include +#include + +#include "core/os/device/deviceinfo/cc/instance.h" +#include "core/os/device/device.pb.h" +#include + +void print_help() { + std::cout << "Usage: device_info [--binary]" << std::endl; + std::cout << "Output information about the current device." << std::endl; + std::cout << " --binary Output a binary protobuf instead of json" << std::endl; +} + +int main(int argc, char const *argv[]) +{ + bool output_binary = false; + for (size_t i = 1; i < argc; ++i) { + if (strcmp(argv[i], "--help") == 0 || + strcmp(argv[i], "-help") == 0 || + strcmp(argv[i], "-h") == 0) { + print_help(); + return 0; + } else if (strcmp(argv[i], "--binary") == 0) { + output_binary = true; + } else { + print_help(); + return -1; + } + } + + device_instance instance = get_device_instance(nullptr); + if (output_binary) { +#if _WIN32 + _setmode(_fileno(stdout), _O_BINARY); +#endif + std::cout << std::string(reinterpret_cast(instance.data), instance.size); + } else { + device::Instance device_inst; + if (!device_inst.ParseFromArray(instance.data, instance.size)) { + std::cerr << "Internal error." << std::endl; + free_device_instance(instance); + return -1; + } + std::string output; + google::protobuf::util::JsonPrintOptions options; + options.add_whitespace = true; + if (google::protobuf::util::Status::OK != google::protobuf::util::MessageToJsonString(device_inst, &output, options)) { + std::cerr << "Internal error: Could not convert to json" << std::endl; + free_device_instance(instance); + return -1; + } + std::cout << output; + } + + free_device_instance(instance); + + return 0; +} From 078fb8cda6db836e59c8b77e746be9e0ee4700ca Mon Sep 17 00:00:00 2001 From: Andrew Woloszyn Date: Tue, 22 May 2018 11:19:53 -0400 Subject: [PATCH 02/10] Added Experimental Remote SSH replay support. This allows an ssh configuration file to be created, and used to detect a remote SSH device. --- cmd/gapis/BUILD.bazel | 1 + cmd/gapis/main.go | 48 +++- cmd/gapit/trace.go | 9 +- core/app/layout/layout.go | 93 +++++-- core/app/layout/pkgdata.go | 13 +- core/os/device/remotessh/BUILD.bazel | 53 ++++ core/os/device/remotessh/commands.go | 246 ++++++++++++++++++ core/os/device/remotessh/configuration.go | 77 ++++++ .../os/device/remotessh/configuration_test.go | 88 +++++++ core/os/device/remotessh/device.go | 216 +++++++++++++++ core/os/process/BUILD.bazel | 1 + core/os/process/client.go | 38 +++ core/os/shell/env.go | 12 + core/vulkan/loader/BUILD.bazel | 3 + core/vulkan/loader/loader.go | 150 ++++++++--- gapir/client/BUILD.bazel | 1 + gapir/client/session.go | 121 ++++++++- tools/build/workspace_go.bzl | 25 +- 18 files changed, 1119 insertions(+), 76 deletions(-) create mode 100644 core/os/device/remotessh/BUILD.bazel create mode 100644 core/os/device/remotessh/commands.go create mode 100644 core/os/device/remotessh/configuration.go create mode 100644 core/os/device/remotessh/configuration_test.go create mode 100644 core/os/device/remotessh/device.go diff --git a/cmd/gapis/BUILD.bazel b/cmd/gapis/BUILD.bazel index d3a4223860..b7fd5a4603 100644 --- a/cmd/gapis/BUILD.bazel +++ b/cmd/gapis/BUILD.bazel @@ -29,6 +29,7 @@ go_library( "//core/os/android/adb:go_default_library", "//core/os/device/bind:go_default_library", "//core/os/device/host:go_default_library", + "//core/os/device/remotessh:go_default_library", "//core/os/file:go_default_library", "//core/text:go_default_library", "//gapir/client:go_default_library", diff --git a/cmd/gapis/main.go b/cmd/gapis/main.go index 5e9415148d..ac620ea125 100644 --- a/cmd/gapis/main.go +++ b/cmd/gapis/main.go @@ -17,6 +17,8 @@ package main import ( "context" "flag" + "io" + "os" "path/filepath" "time" @@ -30,6 +32,7 @@ import ( "github.com/google/gapid/core/os/android/adb" "github.com/google/gapid/core/os/device/bind" "github.com/google/gapid/core/os/device/host" + "github.com/google/gapid/core/os/device/remotessh" "github.com/google/gapid/core/os/file" "github.com/google/gapid/core/text" "github.com/google/gapid/gapir/client" @@ -55,6 +58,7 @@ var ( idleTimeout = flag.Duration("idle-timeout", 0, "_Closes GAPIS if the server is not repeatedly pinged within this duration") adbPath = flag.String("adb", "", "Path to the adb executable; leave empty to search the environment") enableLocalFiles = flag.Bool("enable-local-files", false, "Allow clients to access local .gfxtrace files by path") + remoteSSHConfig = flag.String("ssh-config", "", "Path to an ssh config file for remote devices") ) func main() { @@ -101,13 +105,31 @@ func run(ctx context.Context) error { r.SetDeviceProperty(ctx, host, client.LaunchArgsKey, text.SplitArgs(*gapirArgStr)) } - deviceScanDone, onDeviceScanDone := task.NewSignal() + numAsyncScans := 0 + scanDone := make(chan bool) + if *scanAndroidDevs { - crash.Go(func() { monitorAndroidDevices(ctx, r, onDeviceScanDone) }) - } else { - onDeviceScanDone(ctx) + numAsyncScans++ + crash.Go(func() { monitorAndroidDevices(ctx, r, scanDone) }) } + if *remoteSSHConfig != "" { + f, err := os.Open(*remoteSSHConfig) + if err != nil { + return err + } + numAsyncScans++ + crash.Go(func() { getRemoteSSHDevices(ctx, r, f, scanDone) }) + } + + deviceScanDone, onDeviceScanDone := task.NewSignal() + go func() { + for i := 0; i < numAsyncScans; i++ { + <-scanDone + } + onDeviceScanDone(ctx) + }() + return server.Listen(ctx, *rpc, server.Config{ Info: &service.ServerInfo{ Name: host.Instance(ctx).Name, @@ -125,10 +147,10 @@ func run(ctx context.Context) error { }) } -func monitorAndroidDevices(ctx context.Context, r *bind.Registry, onDeviceScanDone task.Task) { +func monitorAndroidDevices(ctx context.Context, r *bind.Registry, scanDone chan bool) { // Populate the registry with all the existing devices. func() { - defer onDeviceScanDone(ctx) // Signal that we have a primed registry. + defer func() { scanDone <- true }() // Signal that we have a primed registry. if devs, err := adb.Devices(ctx); err == nil { for _, d := range devs { @@ -142,6 +164,20 @@ func monitorAndroidDevices(ctx context.Context, r *bind.Registry, onDeviceScanDo } } +func getRemoteSSHDevices(ctx context.Context, r *bind.Registry, f io.Reader, scanDone chan bool) { + // Populate the registry with all the existing devices. + func() { + defer func() { scanDone <- true }() // Signal that we have a primed registry. + + if devs, err := remotessh.Devices(ctx, f); err == nil { + for _, d := range devs { + r.AddDevice(ctx, d) + r.SetDeviceProperty(ctx, d, client.LaunchArgsKey, text.SplitArgs(*gapirArgStr)) + } + } + }() +} + func loadStrings(ctx context.Context) []*stringtable.StringTable { files, err := filepath.Glob(filepath.Join(*stringsPath, "*.stb")) if err != nil { diff --git a/cmd/gapit/trace.go b/cmd/gapit/trace.go index 90242602ac..e49f61ccbd 100644 --- a/cmd/gapit/trace.go +++ b/cmd/gapit/trace.go @@ -103,7 +103,7 @@ func (verb *traceVerb) Run(ctx context.Context, flags flag.FlagSet) error { if !verb.Local.App.IsEmpty() { cleanup, err := verb.startLocalApp(ctx) - defer func() { cleanup() }() + defer func() { cleanup(ctx) }() if err != nil { return err } @@ -139,12 +139,13 @@ func (verb *traceVerb) inputHandler(ctx context.Context, deferStart bool) (conte return ctx, startSignal } -func (verb *traceVerb) startLocalApp(ctx context.Context) (func(), error) { +func (verb *traceVerb) startLocalApp(ctx context.Context) (func(ctx context.Context), error) { // Run the local application with VK_LAYER_PATH, VK_INSTANCE_LAYERS, // VK_DEVICE_LAYERS and LD_PRELOAD set to correct values to load the spy // layer. env := shell.CloneEnv() - cleanup, portFile, err := loader.SetupTrace(ctx, env) + device := bind.Host(ctx) + cleanup, portFile, err := loader.SetupTrace(ctx, device, device.Instance().Configuration.ABIs[0], env) if err != nil { return cleanup, err } @@ -166,7 +167,7 @@ func (verb *traceVerb) startLocalApp(ctx context.Context) (func(), error) { if verb.Local.Port == 0 { verb.Local.Port = boundPort } - return func() { cancel(); cleanup() }, nil + return func(ctx context.Context) { cancel(); cleanup(ctx) }, nil } type traceOptions struct { diff --git a/core/app/layout/layout.go b/core/app/layout/layout.go index e6d1619672..1d7b7d8080 100644 --- a/core/app/layout/layout.go +++ b/core/app/layout/layout.go @@ -49,15 +49,17 @@ type FileLayout interface { // Gapis returns the path to the gapis binary in this layout. Gapis(ctx context.Context) (file.Path, error) // Gapir returns the path to the gapir binary in this layout. - Gapir(ctx context.Context) (file.Path, error) + Gapir(ctx context.Context, abi *device.ABI) (file.Path, error) // GapidApk returns the path to gapid.apk in this layout. GapidApk(ctx context.Context, abi *device.ABI) (file.Path, error) // Library returns the path to the requested library. - Library(ctx context.Context, lib LibraryType) (file.Path, error) + Library(ctx context.Context, lib LibraryType, abi *device.ABI) (file.Path, error) // Json returns the path to the Vulkan layer JSON definition for the given library. Json(ctx context.Context, lib LibraryType) (file.Path, error) // GoArgs returns additional arguments to pass to go binaries. GoArgs(ctx context.Context) []string + // DeviceInfo returns the device info executable for the given ABI + DeviceInfo(ctx context.Context, os device.OSKind) (file.Path, error) } func withExecutablePlatformSuffix(exe string, os device.OSKind) string { @@ -88,6 +90,10 @@ func withLibraryPlatformSuffix(lib string, os device.OSKind) string { } } +func GetLibraryName(lib LibraryType, abi *device.ABI) string { + return withLibraryPlatformSuffix(libTypeToName[lib], abi.OS) +} + var abiToApk = map[device.Architecture]string{ device.ARMv7a: "gapid-armeabi.apk", device.ARMv8a: "gapid-aarch64.apk", @@ -112,8 +118,18 @@ func (l pkgLayout) Gapit(ctx context.Context) (file.Path, error) { return l.root.Join(withExecutablePlatformSuffix("gapit", hostOS(ctx))), nil } -func (l pkgLayout) Gapir(ctx context.Context) (file.Path, error) { - return l.root.Join(withExecutablePlatformSuffix("gapir", hostOS(ctx))), nil +var osToDir = map[device.OSKind]string{ + device.Linux: "linux", + device.OSX: "macos", + device.Windows: "windows", +} + +func (l pkgLayout) Gapir(ctx context.Context, abi *device.ABI) (file.Path, error) { + if hostOS(ctx) == abi.OS { + return l.root.Join(withExecutablePlatformSuffix("gapir", hostOS(ctx))), nil + } else { + return l.root.Join(osToDir[abi.OS], withExecutablePlatformSuffix("gapir", abi.OS)), nil + } } func (l pkgLayout) Gapis(ctx context.Context) (file.Path, error) { @@ -124,8 +140,12 @@ func (l pkgLayout) GapidApk(ctx context.Context, abi *device.ABI) (file.Path, er return l.root.Join(abiToApk[abi.Architecture]), nil } -func (l pkgLayout) Library(ctx context.Context, lib LibraryType) (file.Path, error) { - return l.root.Join("lib", withLibraryPlatformSuffix(libTypeToName[lib], hostOS(ctx))), nil +func (l pkgLayout) Library(ctx context.Context, lib LibraryType, abi *device.ABI) (file.Path, error) { + if hostOS(ctx) == abi.OS { + return l.root.Join("lib", withLibraryPlatformSuffix(libTypeToName[lib], hostOS(ctx))), nil + } else { + return l.root.Join(osToDir[abi.OS], "lib", withLibraryPlatformSuffix(libTypeToName[lib], abi.OS)), nil + } } func (l pkgLayout) Json(ctx context.Context, lib LibraryType) (file.Path, error) { @@ -136,6 +156,14 @@ func (l pkgLayout) GoArgs(ctx context.Context) []string { return nil } +func (l pkgLayout) DeviceInfo(ctx context.Context, os device.OSKind) (file.Path, error) { + if hostOS(ctx) == os { + return l.root.Join(withExecutablePlatformSuffix("device_info", os)), nil + } else { + return l.root.Join(osToDir[os], withExecutablePlatformSuffix("device_info", os)), nil + } +} + // NewPkgLayout returns a FileLayout rooted at the given directory with the standard package layout. // If create is true, the package layout is created if it doesn't exist, otherwise an error is returned. func NewPkgLayout(dir file.Path, create bool) (FileLayout, error) { @@ -220,16 +248,22 @@ func (l *runfilesLayout) Gapis(ctx context.Context) (file.Path, error) { return l.find(withExecutablePlatformSuffix("gapid/cmd/gapis/gapis", hostOS(ctx))) } -func (l *runfilesLayout) Gapir(ctx context.Context) (file.Path, error) { - return l.find(withExecutablePlatformSuffix("gapid/cmd/gapir/cc/gapir", hostOS(ctx))) +func (l *runfilesLayout) Gapir(ctx context.Context, abi *device.ABI) (file.Path, error) { + if hostOS(ctx) == abi.OS { + return l.find(withExecutablePlatformSuffix("gapid/cmd/gapir/cc/gapir", hostOS(ctx))) + } + return file.Path{}, ErrCannotFindPackageFiles } func (l *runfilesLayout) GapidApk(ctx context.Context, abi *device.ABI) (file.Path, error) { return l.find("gapid/gapidapk/android/apk/" + abiToApkPath[abi.Architecture]) } -func (l *runfilesLayout) Library(ctx context.Context, lib LibraryType) (file.Path, error) { - return l.find(withLibraryPlatformSuffix(libTypeToLibPath[lib], hostOS(ctx))) +func (l *runfilesLayout) Library(ctx context.Context, lib LibraryType, abi *device.ABI) (file.Path, error) { + if hostOS(ctx) == abi.OS { + return l.find(withLibraryPlatformSuffix(libTypeToLibPath[lib], hostOS(ctx))) + } + return file.Path{}, ErrCannotFindPackageFiles } func (l *runfilesLayout) Json(ctx context.Context, lib LibraryType) (file.Path, error) { @@ -240,6 +274,13 @@ func (l *runfilesLayout) GoArgs(ctx context.Context) []string { return []string{"-runfiles", l.manifest} } +func (l *runfilesLayout) DeviceInfo(ctx context.Context, os device.OSKind) (file.Path, error) { + if hostOS(ctx) == os { + return l.find("core/os/device/deviceinfo/exe/" + withExecutablePlatformSuffix("device_info", os)) + } + return file.Path{}, ErrCannotFindPackageFiles +} + // unknownLayout is the file layout used when no other layout can be discovered. // All methods will return an error. type unknownLayout struct{} @@ -256,7 +297,7 @@ func (l unknownLayout) Gapis(ctx context.Context) (file.Path, error) { return file.Path{}, ErrCannotFindPackageFiles } -func (l unknownLayout) Gapir(ctx context.Context) (file.Path, error) { +func (l unknownLayout) Gapir(ctx context.Context, abi *device.ABI) (file.Path, error) { return file.Path{}, ErrCannotFindPackageFiles } @@ -264,7 +305,7 @@ func (l unknownLayout) GapidApk(ctx context.Context, abi *device.ABI) (file.Path return file.Path{}, ErrCannotFindPackageFiles } -func (l unknownLayout) Library(ctx context.Context, lib LibraryType) (file.Path, error) { +func (l unknownLayout) Library(ctx context.Context, lib LibraryType, abi *device.ABI) (file.Path, error) { return file.Path{}, ErrCannotFindPackageFiles } @@ -276,6 +317,10 @@ func (l unknownLayout) GoArgs(ctx context.Context) []string { return nil } +func (l unknownLayout) DeviceInfo(ctx context.Context, os device.OSKind) (file.Path, error) { + return file.Path{}, ErrCannotFindPackageFiles +} + // ZipLayout is a FileLayout view over a ZIP file. type ZipLayout struct { f *zip.Reader @@ -317,8 +362,12 @@ func (l *ZipLayout) Gapit(ctx context.Context) (*zip.File, error) { } // Gapir returns the path to the gapir binary in this layout. -func (l *ZipLayout) Gapir(ctx context.Context) (*zip.File, error) { - return l.file(withExecutablePlatformSuffix("gapir", l.os)) +func (l *ZipLayout) Gapir(ctx context.Context, abi *device.ABI) (*zip.File, error) { + if l.os == abi.OS { + return l.file(withExecutablePlatformSuffix("gapir", l.os)) + } else { + return l.file(osToDir[abi.OS] + "/" + withExecutablePlatformSuffix("gapir", abi.OS)) + } } // Gapis returns the path to the gapis binary in this layout. @@ -332,11 +381,23 @@ func (l *ZipLayout) GapidApk(ctx context.Context, abi *device.ABI) (*zip.File, e } // Library returns the path to the requested library. -func (l *ZipLayout) Library(ctx context.Context, lib LibraryType) (*zip.File, error) { - return l.file("lib/" + withLibraryPlatformSuffix(libTypeToName[lib], l.os)) +func (l *ZipLayout) Library(ctx context.Context, lib LibraryType, abi *device.ABI) (*zip.File, error) { + if l.os == abi.OS { + return l.file("lib/" + withLibraryPlatformSuffix(libTypeToName[lib], l.os)) + } else { + return l.file(osToDir[abi.OS] + "lib/" + withLibraryPlatformSuffix(libTypeToName[lib], abi.OS)) + } } // Json returns the path to the Vulkan layer JSON definition for the given library. func (l *ZipLayout) Json(ctx context.Context, lib LibraryType) (*zip.File, error) { return l.file("lib/" + libTypeToJson[lib]) } + +func (l *ZipLayout) DeviceInfo(ctx context.Context, os device.OSKind) (*zip.File, error) { + if l.os == os { + return l.file(withExecutablePlatformSuffix("device_info", os)) + } else { + return l.file(osToDir[os] + "/" + withExecutablePlatformSuffix("device_info", os)) + } +} diff --git a/core/app/layout/pkgdata.go b/core/app/layout/pkgdata.go index 5e4a62ebcc..729ff64ce7 100644 --- a/core/app/layout/pkgdata.go +++ b/core/app/layout/pkgdata.go @@ -94,8 +94,8 @@ func GapidApk(ctx context.Context, abi *device.ABI) (file.Path, error) { } // Gapir returns the path to the gapir binary. -func Gapir(ctx context.Context) (file.Path, error) { - return layout(ctx).Gapir(ctx) +func Gapir(ctx context.Context, abi *device.ABI) (file.Path, error) { + return layout(ctx).Gapir(ctx, abi) } // Gapit returns the path to the gapir binary. @@ -104,8 +104,8 @@ func Gapit(ctx context.Context) (file.Path, error) { } // Library returns the path to the requested library. -func Library(ctx context.Context, lib LibraryType) (file.Path, error) { - return layout(ctx).Library(ctx, lib) +func Library(ctx context.Context, lib LibraryType, abi *device.ABI) (file.Path, error) { + return layout(ctx).Library(ctx, lib, abi) } // Json returns the path to the Vulkan layer JSON definition for the given library. @@ -117,3 +117,8 @@ func Json(ctx context.Context, lib LibraryType) (file.Path, error) { func GoArgs(ctx context.Context) []string { return layout(ctx).GoArgs(ctx) } + +// DeviceInfo returns the device info executable for the given ABI +func DeviceInfo(ctx context.Context, os device.OSKind) (file.Path, error) { + return layout(ctx).DeviceInfo(ctx, os) +} diff --git a/core/os/device/remotessh/BUILD.bazel b/core/os/device/remotessh/BUILD.bazel new file mode 100644 index 0000000000..5cd2ae717d --- /dev/null +++ b/core/os/device/remotessh/BUILD.bazel @@ -0,0 +1,53 @@ +# Copyright (C) 2018 Google Inc. +# +# 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. + +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "device.go", + "configuration.go", + "commands.go", + ], + importpath = "github.com/google/gapid/core/os/device/remotessh", + visibility = ["//visibility:public"], + deps = [ + "//core/os/device/bind:go_default_library", + "//core/log:go_default_library", + "//core/os/device:go_default_library", + "//core/os/shell:go_default_library", + "//core/app/layout:go_default_library", + "@org_golang_x_crypto//ssh:go_default_library", + "@org_golang_x_crypto//ssh/agent:go_default_library", + "@org_golang_x_crypto//ssh/knownhosts:go_default_library", + "@com_github_golang_protobuf//proto:go_default_library", + "@com_github_golang_protobuf//jsonpb:go_default_library", + + ], +) + +go_test( + name = "go_default_xtest", + size = "small", + srcs = [ + "configuration_test.go", + ], + deps = [ + ":go_default_library", + "//core/assert:go_default_library", + "//core/log:go_default_library", + "//core/os/device/host:go_default_library", + ], +) diff --git a/core/os/device/remotessh/commands.go b/core/os/device/remotessh/commands.go new file mode 100644 index 0000000000..a3af1d4a84 --- /dev/null +++ b/core/os/device/remotessh/commands.go @@ -0,0 +1,246 @@ +// Copyright (C) 2018 Google Inc. +// +// 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 remotessh + +import ( + "context" + "fmt" + "io" + "net" + "os" + "strings" + + "github.com/google/gapid/core/log" + "github.com/google/gapid/core/os/device" + "github.com/google/gapid/core/os/shell" + "golang.org/x/crypto/ssh" +) + +// Process is the interface to a running process, as started by a Target. +type remoteProcess struct { + session *ssh.Session + stdoutDone chan error + stderrDone chan error +} + +func (r *remoteProcess) Kill() error { + return r.session.Signal(ssh.SIGSEGV) +} + +func (r *remoteProcess) Wait(ctx context.Context) error { + ret := r.session.Wait() + <-r.stdoutDone + <-r.stderrDone + return ret +} + +var _ shell.Process = (*remoteProcess)(nil) + +type sshShellTarget struct{ b *binding } + +func (t sshShellTarget) Start(cmd shell.Cmd) (shell.Process, error) { + session, err := t.b.connection.NewSession() + if err != nil { + return nil, err + } + p := &remoteProcess{ + session: session, + stdoutDone: make(chan error), + stderrDone: make(chan error), + } + + if cmd.Stdin != nil { + stdin, err := session.StdinPipe() + if err != nil { + return nil, err + } + go func() { + defer stdin.Close() + io.Copy(stdin, cmd.Stdin) + }() + } + + if cmd.Stdout != nil { + stdout, err := session.StdoutPipe() + if err != nil { + return nil, err + } + go func() { + b := make([]byte, 1024) + for { + n, err := stdout.Read(b) + cmd.Stdout.Write(b[:n]) + if err != nil { + if err == io.EOF { + p.stdoutDone <- nil + } else { + p.stdoutDone <- err + } + break + } + } + }() + } else { + p.stdoutDone <- nil + } + + if cmd.Stderr != nil { + stderr, err := session.StderrPipe() + if err != nil { + return nil, err + } + go func() { + b := make([]byte, 1024) + for { + n, err := stderr.Read(b) + cmd.Stderr.Write(b[:n]) + if err != nil { + if err == io.EOF { + p.stderrDone <- nil + } else { + p.stderrDone <- err + } + break + } + } + }() + } else { + p.stderrDone <- nil + } + + prefix := "" + if cmd.Dir != "" { + prefix += "cd " + cmd.Dir + "; " + } + + for _, e := range cmd.Environment.Keys() { + if e != "" { + prefix = prefix + e + "=" + cmd.Environment.Get(e) + " " + } + } + + if err := session.Start(prefix + cmd.Name + " " + strings.Join(cmd.Args, " ")); err != nil { + return nil, err + } + + return p, nil +} + +func (t sshShellTarget) String() string { + c := t.b.configuration + return c.User + "@" + c.Host + ": " + t.b.String() +} + +// Shell implements the Device interface returning commands that will error if run. +func (b binding) Shell(name string, args ...string) shell.Cmd { + return shell.Command(name, args...).On(sshShellTarget{&b}) +} + +func (b binding) destroyPosixDirectory(ctx context.Context, dir string) { + _, _ = b.Shell("rm", "-rf", dir).Call(ctx) +} + +func (b binding) createPosixTempDirectory(ctx context.Context) (string, func(context.Context), error) { + dir, err := b.Shell("mktemp", "-d").Call(ctx) + if err != nil { + return "", nil, err + } + return dir, func(ctx context.Context) { b.destroyPosixDirectory(ctx, dir) }, nil +} + +func (b binding) createWindowsTempDirectory(ctx context.Context) (string, func(ctx context.Context), error) { + return "", nil, fmt.Errorf("Not yet supported, windows remote targets") +} + +func (b binding) MakeTempDir(ctx context.Context) (string, func(ctx context.Context), error) { + switch b.os { + case device.Linux: + fallthrough + case device.OSX: + return b.createPosixTempDirectory(ctx) + case device.Windows: + return b.createWindowsTempDirectory(ctx) + default: + panic("We should never end up here") + } +} + +func (b binding) WriteFile(ctx context.Context, contents io.Reader, mode string, destPath string) error { + _, err := b.Shell("cat", ">", destPath, "; chmod ", mode, " ", destPath).Read(contents).Call(ctx) + return err +} + +func (b binding) PushFile(ctx context.Context, source, dest string) error { + infile, err := os.Open(source) + if err != nil { + return err + } + permission, err := os.Stat(source) + if err != nil { + return err + } + perm := fmt.Sprintf("%4o", permission.Mode().Perm()) + + return b.WriteFile(ctx, infile, perm, dest) +} + +func (b binding) doTunnel(ctx context.Context, local net.Conn, remotePort int) error { + remote, err := b.connection.Dial("tcp", fmt.Sprintf("localhost:%d", remotePort)) + if err != nil { + local.Close() + return err + } + + closer := make(chan bool) + + copy := func(writer io.Writer, reader io.Reader) { + _, err := io.Copy(writer, reader) + if err != nil { + log.E(ctx, "Copy Error %s", err) + } + closer <- true + } + + go copy(local, remote) + go copy(remote, local) + go func() { + defer local.Close() + defer remote.Close() + // When one direction of the communication has + // closed, close the entire connection + <-closer + <-closer + }() + return nil +} + +func (b binding) ForwardPort(ctx context.Context, remotePort int) (int, error) { + listener, err := net.Listen("tcp", ":0") + if err != nil { + return 0, err + } + go func() { + defer listener.Close() + for { + local, err := listener.Accept() + if err != nil { + return + } + go b.doTunnel(ctx, local, remotePort) + } + }() + + return listener.Addr().(*net.TCPAddr).Port, nil +} diff --git a/core/os/device/remotessh/configuration.go b/core/os/device/remotessh/configuration.go new file mode 100644 index 0000000000..bcc2c176fd --- /dev/null +++ b/core/os/device/remotessh/configuration.go @@ -0,0 +1,77 @@ +// Copyright (C) 2018 Google Inc. +// +// 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 remotessh + +import ( + "encoding/json" + "io" + "io/ioutil" + "os/user" +) + +// Configuration represents a configuration for connecting +// to an SSH remote client. +// The SSH agent is first used to attempt connection, +// followed by the given Keyfile +type Configuration struct { + // The name to use for this connection + Name string + // The hostname to connect to + Host string + // User is the username to use for login + User string + // Which port should be used + Port int16 + // The pem encoded public key file to use for the connection. + // If not specified uses ~/.ssh/id_rsa + Keyfile string + // The known_hosts file to use for authentication. Defaults to + // ~/.ssh/known_hosts + KnownHosts string +} + +func (c *Configuration) UnmarshalJSON(data []byte) error { + type configAlias Configuration + u, err := user.Current() + if err != nil { + return err + } + newC := &configAlias{ + Name: "", + Host: "", + User: u.Username, + Port: 22, + Keyfile: u.HomeDir + "/.ssh/id_rsa", + KnownHosts: u.HomeDir + "/.ssh/known_hosts", + } + if err := json.Unmarshal(data, newC); err != nil { + return err + } + + *c = Configuration(*newC) + return nil +} + +func ReadConfiguration(r io.Reader) ([]Configuration, error) { + cfg := []Configuration{} + + bytes, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + + err = json.Unmarshal(bytes, &cfg) + return cfg, err +} diff --git a/core/os/device/remotessh/configuration_test.go b/core/os/device/remotessh/configuration_test.go new file mode 100644 index 0000000000..59cb98fbb2 --- /dev/null +++ b/core/os/device/remotessh/configuration_test.go @@ -0,0 +1,88 @@ +// Copyright (C) 2017 Google Inc. +// +// 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 remotessh_test + +import ( + "bytes" + "testing" + + "github.com/google/gapid/core/assert" + "github.com/google/gapid/core/log" + "github.com/google/gapid/core/os/device/remotessh" +) + +func TestReadConfiguration(t *testing.T) { + ctx := log.Testing(t) + + input := ` +[ + { + "Name": "name", + "Host": "localhost", + "Port": 22, + "User": "me", + "Keyfile": "~/.ssh/id_rsa", + "KnownHosts": "~/.ssh/known_hosts", + "UseSSHAgent": true + }, + { + "Name": "FirstConnection", + "User": "me", + "Host": "example.com", + "Port": 443 + }, + { + "Name": "Connection2", + "Keyfile": "id_dsa", + "KnownHosts": "someFile", + "UseSSHAgent": false, + "User": "me" + } +] +` + reader := bytes.NewReader([]byte(input)) + configs, err := remotessh.ReadConfiguration(reader) + + assert.With(ctx).That(err).Equals(nil) + + for i, test := range []remotessh.Configuration{ + remotessh.Configuration{ + Name: "name", + Host: "localhost", + User: "me", + Port: 22, + Keyfile: "~/.ssh/id_rsa", + KnownHosts: "~/.ssh/known_hosts", + }, + remotessh.Configuration{ + Name: "FirstConnection", + Host: "example.com", + User: "me", + Port: 443, + Keyfile: "~/.ssh/id_rsa", + KnownHosts: "~/.ssh/known_hosts", + }, + remotessh.Configuration{ + Name: "Connection2", + User: "me", + Host: "", + Port: 22, + Keyfile: "id_dsa", + KnownHosts: "someFile", + }, + } { + assert.With(ctx).That(configs[i]).Equals(test) + } +} diff --git a/core/os/device/remotessh/device.go b/core/os/device/remotessh/device.go new file mode 100644 index 0000000000..cd7a1293aa --- /dev/null +++ b/core/os/device/remotessh/device.go @@ -0,0 +1,216 @@ +// Copyright (C) 2018 Google Inc. +// +// 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 remotessh + +import ( + "bytes" + "context" + "fmt" + "io" + "io/ioutil" + "net" + "os" + "strings" + + "github.com/golang/protobuf/jsonpb" + "github.com/google/gapid/core/app/layout" + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/agent" + "golang.org/x/crypto/ssh/knownhosts" + + "github.com/google/gapid/core/os/device" + "github.com/google/gapid/core/os/device/bind" +) + +// Device extends the bind.Device interface with capabilities specific to +// remote SSH clients +type Device interface { + bind.Device + // PushFile will transfer the local file at sourcePath to the remote + // machine at destPath + PushFile(ctx context.Context, sourcePath, destPath string) error + // Forward will forward the local port to the remote port + Forward(ctx context.Context, local, device uint16) error + // RemoveForward removes a port forward from local + RemoveForward(ctx context.Context, local uint16) error + // MakeTempDir makes a temporary directory, and returns the + // path, as well as a function to call to clean it up. + MakeTempDir(ctx context.Context) (string, func(ctx context.Context), error) + // WriteFile writes the given file into the given location on the remote device + WriteFile(ctx context.Context, contents io.Reader, mode string, destPath string) error + // ForwardPort forwards the remote port. It automatically selects an open + // local port. + ForwardPort(ctx context.Context, remoteport int) (int, error) +} + +// binding represents an attached SSH client. +type binding struct { + bind.Simple + + connection *ssh.Client + configuration *Configuration + // We duplicate OS here because we need to use it + // before we get the rest of the information + os device.OSKind +} + +var _ Device = &binding{} + +func (binding) Forward(ctx context.Context, local, device uint16) error { + return nil +} + +func (binding) RemoveForward(ctx context.Context, local uint16) error { + return nil +} + +// Devices returns the list of reachable SSH devices. +func Devices(ctx context.Context, configuration io.Reader) ([]bind.Device, error) { + configurations, err := ReadConfiguration(configuration) + if err != nil { + return nil, err + } + devices := make([]bind.Device, 0, len(configurations)) + + for _, cfg := range configurations { + if device, err := GetConnectedDevice(ctx, cfg); err == nil { + devices = append(devices, device) + } + } + + return devices, nil +} + +func GetSSHAgent() ssh.AuthMethod { + if sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil { + return ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers) + } + return nil +} + +func GetPrivateKeyAuth(path string) (ssh.AuthMethod, error) { + bytes, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + signer, err := ssh.ParsePrivateKey(bytes) + if err != nil { + return nil, err + } + return ssh.PublicKeys(signer), nil +} + +// GetConnectedDevice returns a device that has an option connection. +func GetConnectedDevice(ctx context.Context, c Configuration) (Device, error) { + auths := []ssh.AuthMethod{} + if agent := GetSSHAgent(); agent != nil { + auths = append(auths, agent) + } + + if c.Keyfile != "" { + if auth, err := GetPrivateKeyAuth(c.Keyfile); err == nil { + auths = append(auths, auth) + } + } + + if len(auths) == 0 { + return nil, fmt.Errorf("No valid authentication method for SSH connection %s", c.Name) + } + + hosts, err := knownhosts.New(c.KnownHosts) + if err != nil { + return nil, fmt.Errorf("Could not read known hosts %v", err) + } + + sshConfig := &ssh.ClientConfig{ + User: c.User, + Auth: auths, + HostKeyCallback: hosts, + } + + connection, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", c.Host, c.Port), sshConfig) + if err != nil { + return nil, err + } + + b := &binding{ + connection: connection, + configuration: &c, + Simple: bind.Simple{ + To: &device.Instance{ + Serial: "", + Configuration: &device.Configuration{}, + }, + LastStatus: bind.Status_Online, + }, + } + + kind := device.UnknownOS + + // Try to get the OS string for Mac/Linux + if osName, err := b.Shell("uname", "-a").Call(ctx); err == nil { + if strings.Contains(osName, "Darwin") { + kind = device.OSX + } else if strings.Contains(osName, "Linux") { + kind = device.Linux + } + } + + if kind == device.UnknownOS { + // Try to get the OS string for Windows + if osName, err := b.Shell("ver").Call(ctx); err == nil { + if strings.Contains(osName, "Windows") { + kind = device.Windows + } + } + } + + if kind == device.UnknownOS { + return nil, fmt.Errorf("Could not determine unix type") + } + b.os = kind + + dir, cleanup, err := b.MakeTempDir(ctx) + if err != nil { + return nil, err + } + defer cleanup(ctx) + + localDeviceInfo, err := layout.DeviceInfo(ctx, b.os) + if err != nil { + return nil, err + } + + if err = b.PushFile(ctx, localDeviceInfo.System(), dir+"/device_info"); err != nil { + return nil, err + } + + devInfo, err := b.Shell("./device_info").In(dir).Call(ctx) + + if err != nil { + return nil, err + } + + var device device.Instance + + if err := jsonpb.Unmarshal(bytes.NewReader([]byte(devInfo)), &device); err != nil { + panic(err) + } + + b.To = &device + b.To.Name = c.Name + return b, nil +} diff --git a/core/os/process/BUILD.bazel b/core/os/process/BUILD.bazel index 79068441bb..7c8f7b4c3d 100644 --- a/core/os/process/BUILD.bazel +++ b/core/os/process/BUILD.bazel @@ -27,5 +27,6 @@ go_library( "//core/app/crash:go_default_library", "//core/event/task:go_default_library", "//core/os/shell:go_default_library", + "//core/os/device/bind:go_default_library", ], ) diff --git a/core/os/process/client.go b/core/os/process/client.go index 1c27efca2b..04e1bb38d1 100644 --- a/core/os/process/client.go +++ b/core/os/process/client.go @@ -28,6 +28,7 @@ import ( "github.com/google/gapid/core/app/auth" "github.com/google/gapid/core/app/crash" "github.com/google/gapid/core/event/task" + "github.com/google/gapid/core/os/device/bind" "github.com/google/gapid/core/os/shell" ) @@ -126,6 +127,43 @@ type StartOptions struct { WorkingDir string } +// Start runs the application with the given path and options, waits for +// the "Bound on port {port}" string to be printed to stdout, and then returns +// the port number. +func StartOnDevice(ctx context.Context, bind bind.Device, name string, opts StartOptions) (int, error) { + // Append extra environment variable values + errChan := make(chan error, 1) + portChan := make(chan string, 1) + stdout := &portWatcher{ + portChan: portChan, + stdout: opts.Stdout, + portFile: opts.PortFile, + } + + c, cancel := task.WithCancel(ctx) + defer cancel() + crash.Go(func() { + stdout.waitForFile(c) + }) + crash.Go(func() { + cmd := bind.Shell(name, opts.Args...). + In(opts.WorkingDir). + Env(opts.Env). + Capture(stdout, opts.Stderr) + if opts.Verbose { + cmd = cmd.Verbose() + } + errChan <- cmd.Run(ctx) + }) + + select { + case port := <-portChan: + return strconv.Atoi(port) + case err := <-errChan: + return 0, err + } +} + // Start runs the application with the given path and options, waits for // the "Bound on port {port}" string to be printed to stdout, and then returns // the port number. diff --git a/core/os/shell/env.go b/core/os/shell/env.go index 9cae632cb9..f16e9e9ee3 100644 --- a/core/os/shell/env.go +++ b/core/os/shell/env.go @@ -73,6 +73,18 @@ func (e *Env) Vars() []string { return out } +// Keys returns all of the environment variable keys. +func (e *Env) Keys() []string { + if e == nil { + return nil + } + out := make([]string, len(e.keys)) + for k := range e.keys { + out = append(out, k) + } + return out +} + // Exists returns true if the environment variable with the specified key // exists. // The variable can be of the form 'key=value' or simply 'key'. diff --git a/core/vulkan/loader/BUILD.bazel b/core/vulkan/loader/BUILD.bazel index f85101946d..2798aa755f 100644 --- a/core/vulkan/loader/BUILD.bazel +++ b/core/vulkan/loader/BUILD.bazel @@ -21,6 +21,9 @@ go_library( visibility = ["//visibility:public"], deps = [ "//core/app/layout:go_default_library", + "//core/os/device/remotessh:go_default_library", + "//core/os/device/bind:go_default_library", + "//core/os/device:go_default_library", "//core/os/file:go_default_library", "//core/os/shell:go_default_library", ], diff --git a/core/vulkan/loader/loader.go b/core/vulkan/loader/loader.go index 79dbc0234a..38bcbcfc63 100644 --- a/core/vulkan/loader/loader.go +++ b/core/vulkan/loader/loader.go @@ -16,6 +16,7 @@ package loader import ( + "bytes" "context" "io/ioutil" "os" @@ -23,33 +24,119 @@ import ( "strings" "github.com/google/gapid/core/app/layout" + "github.com/google/gapid/core/os/device" + "github.com/google/gapid/core/os/device/bind" + "github.com/google/gapid/core/os/device/remotessh" "github.com/google/gapid/core/os/file" "github.com/google/gapid/core/os/shell" ) +// DeviceSetup handles getting files from/to the right location +// for a particular Device +type DeviceReplaySetup interface { + // MakeTempDir returns a path to a created temporary + MakeTempDir(ctx context.Context) (string, func(ctx context.Context), error) + + // InitializeLibrary takes a library, and if necessary copies it + // into the given temporary directory. It returns the library + // location if necessary. + InitializeLibrary(ctx context.Context, tempdir string, library layout.LibraryType) (string, error) + + // FinalizeJSON puts the given JSON content in the given file + FinalizeJSON(ctx context.Context, jsonName string, content string) (string, error) +} + +type remoteSetup struct { + device remotessh.Device + abi *device.ABI +} + +func (r *remoteSetup) MakeTempDir(ctx context.Context) (string, func(ctx context.Context), error) { + return r.device.MakeTempDir(ctx) +} + +func (r *remoteSetup) InitializeLibrary(ctx context.Context, tempdir string, library layout.LibraryType) (string, error) { + lib, err := layout.Library(ctx, library, r.abi) + if err != nil { + return "", err + } + libName := layout.GetLibraryName(library, r.abi) + if err := r.device.PushFile(ctx, lib.System(), tempdir+"/"+libName); err != nil { + return "", err + } + return tempdir + "/" + libName, nil +} + +func (r *remoteSetup) FinalizeJSON(ctx context.Context, jsonName string, content string) (string, error) { + if err := r.device.WriteFile(ctx, bytes.NewReader([]byte(content)), "0644", jsonName); err != nil { + return "", err + } + return jsonName, nil +} + +type localSetup struct { + abi *device.ABI +} + +func (*localSetup) MakeTempDir(ctx context.Context) (string, func(ctx context.Context), error) { + tempdir, err := ioutil.TempDir("", "temp") + if err != nil { + return "", nil, err + } + return tempdir, func(ctx context.Context) { + os.RemoveAll(tempdir) + }, nil +} + +func (l *localSetup) InitializeLibrary(ctx context.Context, tempdir string, library layout.LibraryType) (string, error) { + lib, err := layout.Library(ctx, library, l.abi) + if err != nil { + return "", err + } + return lib.System(), nil +} + +func (*localSetup) FinalizeJSON(ctx context.Context, jsonName string, content string) (string, error) { + if err := ioutil.WriteFile(jsonName, []byte(content), 0644); err != nil { + return "", err + } + return jsonName, nil +} + // SetupTrace sets up the environment for tracing a local app. Returns a // clean-up function to be called after the trace completes, and a temporary // filename that can be used to find the port if stdout fails, or an error. -func SetupTrace(ctx context.Context, env *shell.Env) (func(), string, error) { - lib, json, err := findLibraryAndJSON(ctx, layout.LibGraphicsSpy) +func SetupTrace(ctx context.Context, d bind.Device, abi *device.ABI, env *shell.Env) (func(ctx context.Context), string, error) { + var setup DeviceReplaySetup + if dev, ok := d.(remotessh.Device); ok { + setup = &remoteSetup{dev, abi} + } else { + setup = &localSetup{abi} + } + tempdir, cleanup, err := setup.MakeTempDir(ctx) + if err != nil { + return func(ctx context.Context) {}, "", err + } + + lib, json, err := findLibraryAndJSON(ctx, setup, tempdir, layout.LibGraphicsSpy) var f string if err != nil { - return func() {}, f, err + return func(ctx context.Context) {}, f, err } - cleanup, err := setupJSON(lib, json, env) + err = setupJSON(ctx, lib, json, setup, tempdir, env) if err == nil { if fl, e := ioutil.TempFile("", "gapii_port"); e == nil { err = e f = fl.Name() fl.Close() o := cleanup - cleanup = func() { - o() + cleanup = func(ctx context.Context) { + o(ctx) os.Remove(f) } } if err == nil { - env.Set("LD_PRELOAD", lib.System()). + env.Set("LD_PRELOAD", lib). AddPathStart("VK_INSTANCE_LAYERS", "VkGraphicsSpy"). AddPathStart("VK_DEVICE_LAYERS", "VkGraphicsSpy"). Set("GAPII_PORT_FILE", f) @@ -69,46 +156,51 @@ func SetupTrace(ctx context.Context, env *shell.Env) (func(), string, error) { // SetupReplay sets up the environment for local replay. Returns a clean-up // function to be called after replay completes, or an error. -func SetupReplay(ctx context.Context, env *shell.Env) (func(), error) { - lib, json, err := findLibraryAndJSON(ctx, layout.LibVirtualSwapChain) +func SetupReplay(ctx context.Context, d bind.Device, abi *device.ABI, env *shell.Env) (func(ctx context.Context), error) { + var setup DeviceReplaySetup + if dev, ok := d.(remotessh.Device); ok { + setup = &remoteSetup{dev, abi} + } else { + setup = &localSetup{abi} + } + tempdir, cleanup, err := setup.MakeTempDir(ctx) + if err != nil { + return nil, err + } + + lib, json, err := findLibraryAndJSON(ctx, setup, tempdir, layout.LibVirtualSwapChain) if err != nil { - return func() {}, err + return func(ctx context.Context) {}, err } - return setupJSON(lib, json, env) + + err = setupJSON(ctx, lib, json, setup, tempdir, env) + return cleanup, err } -func findLibraryAndJSON(ctx context.Context, libType layout.LibraryType) (file.Path, file.Path, error) { - lib, err := layout.Library(ctx, libType) +func findLibraryAndJSON(ctx context.Context, rs DeviceReplaySetup, tempdir string, libType layout.LibraryType) (string, file.Path, error) { + lib, err := rs.InitializeLibrary(ctx, tempdir, libType) if err != nil { - return file.Path{}, file.Path{}, err + return "", file.Path{}, err } json, err := layout.Json(ctx, libType) if err != nil { - return file.Path{}, file.Path{}, err + return "", file.Path{}, err } return lib, json, nil } -func setupJSON(library, json file.Path, env *shell.Env) (func(), error) { - cleanup := func() {} - +func setupJSON(ctx context.Context, library string, json file.Path, rs DeviceReplaySetup, tempdir string, env *shell.Env) error { sourceContent, err := ioutil.ReadFile(json.System()) if err != nil { - return cleanup, err - } - tempdir, err := ioutil.TempDir("", "gapit_dir") - if err != nil { - return cleanup, err - } - cleanup = func() { - os.RemoveAll(tempdir) + return err } - libName := strings.Replace(library.System(), "\\", "\\\\", -1) + libName := strings.Replace(library, "\\", "\\\\", -1) fixedContent := strings.Replace(string(sourceContent[:]), "", libName, 1) - ioutil.WriteFile(tempdir+"/"+json.Basename(), []byte(fixedContent), 0644) + + rs.FinalizeJSON(ctx, tempdir+"/"+json.Basename(), fixedContent) env.AddPathStart("VK_LAYER_PATH", tempdir) - return cleanup, nil + return nil } diff --git a/gapir/client/BUILD.bazel b/gapir/client/BUILD.bazel index fcc140f082..f2ad5e544d 100644 --- a/gapir/client/BUILD.bazel +++ b/gapir/client/BUILD.bazel @@ -37,6 +37,7 @@ go_library( "//core/os/device:go_default_library", "//core/os/device/bind:go_default_library", "//core/os/device/host:go_default_library", + "//core/os/device/remotessh:go_default_library", "//core/os/process:go_default_library", "//core/os/shell:go_default_library", "//core/text:go_default_library", diff --git a/gapir/client/session.go b/gapir/client/session.go index c825572711..26d34beedc 100644 --- a/gapir/client/session.go +++ b/gapir/client/session.go @@ -32,6 +32,7 @@ import ( "github.com/google/gapid/core/os/device" "github.com/google/gapid/core/os/device/bind" "github.com/google/gapid/core/os/device/host" + "github.com/google/gapid/core/os/device/remotessh" "github.com/google/gapid/core/os/process" "github.com/google/gapid/core/os/shell" "github.com/google/gapid/core/text" @@ -40,7 +41,7 @@ import ( ) const ( - sessionTimeout = time.Second * 10 + sessionTimeout = time.Second * 30 maxCheckSocketFileAttempts = 10 checkSocketFileRetryDelay = time.Second connectTimeout = time.Second * 10 @@ -62,14 +63,16 @@ func newSession(d bind.Device) *session { func (s *session) init(ctx context.Context, d bind.Device, abi *device.ABI, launchArgs []string) error { defer close(s.inited) - var err error + if host.Instance(ctx).SameAs(d.Instance()) { - err = s.newHost(ctx, d, launchArgs) - } else if d, ok := d.(adb.Device); ok { - err = s.newADB(ctx, d, abi) + err = s.newHost(ctx, d, abi, launchArgs) + } else if adbd, ok := d.(adb.Device); ok { + err = s.newADB(ctx, adbd, abi) + } else if remoted, ok := d.(remotessh.Device); ok { + err = s.newRemote(ctx, remoted, abi, launchArgs) } else { - err = log.Errf(ctx, nil, "Cannot connect to device type %v", d) + err = log.Errf(ctx, nil, "Cannot connect to device type %+v", d) } if err != nil { s.close() @@ -80,8 +83,106 @@ func (s *session) init(ctx context.Context, d bind.Device, abi *device.ABI, laun return nil } +func (s *session) newRemote(ctx context.Context, d remotessh.Device, abi *device.ABI, launchArgs []string) error { + authTokenFile, authToken := auth.GenTokenFile() + defer os.Remove(authTokenFile) + + otherdir, cleanup, err := d.MakeTempDir(ctx) + if err != nil { + return err + } + defer cleanup(ctx) + + pf := otherdir + "/auth" + if err = d.PushFile(ctx, authTokenFile, pf); err != nil { + return err + } + + args := []string{ + "--idle-timeout-sec", strconv.Itoa(int(sessionTimeout / time.Second)), + "--auth-token-file", pf, + } + args = append(args, launchArgs...) + + gapir, err := layout.Gapir(ctx, abi) + if err = d.PushFile(ctx, gapir.System(), otherdir+"/gapir"); err != nil { + return err + } + + remoteGapir := otherdir + "/gapir" + + env := shell.NewEnv() + sessionCleanup, err := loader.SetupReplay(ctx, d, abi, env) + if err != nil { + return err + } + s.onClose(func() { sessionCleanup(ctx) }) + + parser := func(severity log.Severity) io.WriteCloser { + h := log.GetHandler(ctx) + if h == nil { + return nil + } + ctx := log.PutProcess(ctx, "gapir") + ctx = log.PutFilter(ctx, nil) + return text.Writer(func(line string) error { + if m := parseHostLogMsg(line); m != nil { + h.Handle(m) + return nil + } + log.From(ctx).Log(severity, false, line) + return nil + }) + } + + stdout := parser(log.Info) + if stdout != nil { + defer stdout.Close() + } + + stderr := parser(log.Error) + if stderr != nil { + defer stderr.Close() + } + + log.I(ctx, "Starting gapir on remote: %v %v", remoteGapir, args) + + port, err := process.StartOnDevice(ctx, d, remoteGapir, process.StartOptions{ + Env: env, + Args: args, + Stdout: stdout, + Stderr: stderr, + }) + + if err != nil { + log.E(ctx, "Starting gapir. Error: %v", err) + return nil + } + + localport, err := d.ForwardPort(ctx, port) + if err != nil { + return err + } + + s.conn, err = newConnection(fmt.Sprintf("localhost:%d", localport), authToken, connectTimeout) + if err != nil { + return log.Err(ctx, err, "Timeout waiting for connection") + } + + s.onClose(func() { + if s.conn != nil { + s.conn.Close() + } + }) + log.I(ctx, "Heartbeat connection setup done") + + s.port = localport + s.auth = authToken + return nil +} + // newHost spawns and returns a new GAPIR instance on the host machine. -func (s *session) newHost(ctx context.Context, d bind.Device, launchArgs []string) error { +func (s *session) newHost(ctx context.Context, d bind.Device, abi *device.ABI, launchArgs []string) error { authTokenFile, authToken := auth.GenTokenFile() defer os.Remove(authTokenFile) @@ -91,16 +192,18 @@ func (s *session) newHost(ctx context.Context, d bind.Device, launchArgs []strin } args = append(args, launchArgs...) - gapir, err := layout.Gapir(ctx) + gapir, err := layout.Gapir(ctx, abi) if err != nil { log.F(ctx, true, "Couldn't locate gapir executable: %v", err) return nil } env := shell.CloneEnv() - if _, err := loader.SetupReplay(ctx, env); err != nil { + cleanup, err := loader.SetupReplay(ctx, d, abi, env) + if err != nil { return err } + s.onClose(func() { cleanup(ctx) }) parser := func(severity log.Severity) io.WriteCloser { h := log.GetHandler(ctx) diff --git a/tools/build/workspace_go.bzl b/tools/build/workspace_go.bzl index db7ad83a48..e3bfeb963e 100644 --- a/tools/build/workspace_go.bzl +++ b/tools/build/workspace_go.bzl @@ -62,14 +62,6 @@ def gapid_go_dependencies(): importpath = "google.golang.org/grpc", ) - _maybe(_github_go_repository, - name = "org_golang_x_crypto", - organization = "golang", - project = "crypto", - commit = "dc137beb6cce2043eb6b5f223ab8bf51c32459f4", - importpath = "golang.org/x/crypto", - ) - _maybe(_github_go_repository, name = "org_golang_x_net", organization = "golang", @@ -94,6 +86,23 @@ def gapid_go_dependencies(): importpath = "golang.org/x/tools", ) + _maybe(_github_go_repository, + name = "org_golang_x_crypto", + organization = "golang", + project = "crypto", + commit = "1a580b3eff7814fc9b40602fd35256c63b50f491", + importpath = "golang.org/x/crypto", + ) + + _maybe(_github_go_repository, + name = "org_golang_x_crypto_ssh_knownhosts", + organization = "golang", + project = "knownhosts", + commit = "1a580b3eff7814fc9b40602fd35256c63b50f491", + importpath = "golang.org/x/crypto/ssh/knownhosts", + ) + + def _maybe(repo_rule, name, **kwargs): if name not in native.existing_rules(): repo_rule(name = name, **kwargs) From 2bd9dc0e78201579eefee9e24bc41f0ba92f10ea Mon Sep 17 00:00:00 2001 From: Andrew Woloszyn Date: Tue, 22 May 2018 11:25:01 -0400 Subject: [PATCH 03/10] Hide the remoteSSHConfig flag by default. --- cmd/gapis/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/gapis/main.go b/cmd/gapis/main.go index ac620ea125..5bd159e6dc 100644 --- a/cmd/gapis/main.go +++ b/cmd/gapis/main.go @@ -58,7 +58,7 @@ var ( idleTimeout = flag.Duration("idle-timeout", 0, "_Closes GAPIS if the server is not repeatedly pinged within this duration") adbPath = flag.String("adb", "", "Path to the adb executable; leave empty to search the environment") enableLocalFiles = flag.Bool("enable-local-files", false, "Allow clients to access local .gfxtrace files by path") - remoteSSHConfig = flag.String("ssh-config", "", "Path to an ssh config file for remote devices") + remoteSSHConfig = flag.String("ssh-config", "", "_Path to an ssh config file for remote devices") ) func main() { From ca133e31e83b980eb54c69e4ba400cebb064061f Mon Sep 17 00:00:00 2001 From: Andrew Woloszyn Date: Tue, 22 May 2018 11:39:56 -0400 Subject: [PATCH 04/10] A few minor cleanups and some additional documentation. --- core/app/layout/layout.go | 4 ++- core/app/layout/pkgdata.go | 2 +- core/os/device/remotessh/commands.go | 10 ++++++++ core/os/device/remotessh/configuration.go | 5 ++++ core/os/device/remotessh/device.go | 31 +++++++++-------------- core/os/process/client.go | 6 ++--- core/vulkan/loader/loader.go | 16 +++++++----- 7 files changed, 43 insertions(+), 31 deletions(-) diff --git a/core/app/layout/layout.go b/core/app/layout/layout.go index 1d7b7d8080..510c047456 100644 --- a/core/app/layout/layout.go +++ b/core/app/layout/layout.go @@ -58,7 +58,7 @@ type FileLayout interface { Json(ctx context.Context, lib LibraryType) (file.Path, error) // GoArgs returns additional arguments to pass to go binaries. GoArgs(ctx context.Context) []string - // DeviceInfo returns the device info executable for the given ABI + // DeviceInfo returns the device info executable for the given ABI. DeviceInfo(ctx context.Context, os device.OSKind) (file.Path, error) } @@ -90,6 +90,7 @@ func withLibraryPlatformSuffix(lib string, os device.OSKind) string { } } +// GetLibraryName returns the filename of the given Library. func GetLibraryName(lib LibraryType, abi *device.ABI) string { return withLibraryPlatformSuffix(libTypeToName[lib], abi.OS) } @@ -394,6 +395,7 @@ func (l *ZipLayout) Json(ctx context.Context, lib LibraryType) (*zip.File, error return l.file("lib/" + libTypeToJson[lib]) } +// DeviceInfo returns the device info executable for the given ABI. func (l *ZipLayout) DeviceInfo(ctx context.Context, os device.OSKind) (*zip.File, error) { if l.os == os { return l.file(withExecutablePlatformSuffix("device_info", os)) diff --git a/core/app/layout/pkgdata.go b/core/app/layout/pkgdata.go index 729ff64ce7..605d58afc6 100644 --- a/core/app/layout/pkgdata.go +++ b/core/app/layout/pkgdata.go @@ -118,7 +118,7 @@ func GoArgs(ctx context.Context) []string { return layout(ctx).GoArgs(ctx) } -// DeviceInfo returns the device info executable for the given ABI +// DeviceInfo returns the device info executable for the given ABI. func DeviceInfo(ctx context.Context, os device.OSKind) (file.Path, error) { return layout(ctx).DeviceInfo(ctx, os) } diff --git a/core/os/device/remotessh/commands.go b/core/os/device/remotessh/commands.go index a3af1d4a84..d3af0282c9 100644 --- a/core/os/device/remotessh/commands.go +++ b/core/os/device/remotessh/commands.go @@ -50,6 +50,7 @@ var _ shell.Process = (*remoteProcess)(nil) type sshShellTarget struct{ b *binding } +// Start starts the given command in the remote shell. func (t sshShellTarget) Start(cmd shell.Cmd) (shell.Process, error) { session, err := t.b.connection.NewSession() if err != nil { @@ -164,6 +165,8 @@ func (b binding) createWindowsTempDirectory(ctx context.Context) (string, func(c return "", nil, fmt.Errorf("Not yet supported, windows remote targets") } +// MakeTempDir creates a temporary directory on the remote machine. It returns the +// full path, and a function that can be called to clean up the directory. func (b binding) MakeTempDir(ctx context.Context) (string, func(ctx context.Context), error) { switch b.os { case device.Linux: @@ -177,11 +180,15 @@ func (b binding) MakeTempDir(ctx context.Context) (string, func(ctx context.Cont } } +// WriteFile moves the contents of io.Reader into the given file on the remote machine. +// The file is given the mode as described by the unix filemode string. func (b binding) WriteFile(ctx context.Context, contents io.Reader, mode string, destPath string) error { _, err := b.Shell("cat", ">", destPath, "; chmod ", mode, " ", destPath).Read(contents).Call(ctx) return err } +// PushFile copies a file from a local path to the remote machine. Permissions are +// maintained across. func (b binding) PushFile(ctx context.Context, source, dest string) error { infile, err := os.Open(source) if err != nil { @@ -196,6 +203,7 @@ func (b binding) PushFile(ctx context.Context, source, dest string) error { return b.WriteFile(ctx, infile, perm, dest) } +// doTunnel tunnels a single connection through the SSH connection. func (b binding) doTunnel(ctx context.Context, local net.Conn, remotePort int) error { remote, err := b.connection.Dial("tcp", fmt.Sprintf("localhost:%d", remotePort)) if err != nil { @@ -226,6 +234,8 @@ func (b binding) doTunnel(ctx context.Context, local net.Conn, remotePort int) e return nil } +// ForwardPort forwards a local TCP port to the remote machine on the remote port. +// The local port that was opened is returned. func (b binding) ForwardPort(ctx context.Context, remotePort int) (int, error) { listener, err := net.Listen("tcp", ":0") if err != nil { diff --git a/core/os/device/remotessh/configuration.go b/core/os/device/remotessh/configuration.go index bcc2c176fd..56001020d1 100644 --- a/core/os/device/remotessh/configuration.go +++ b/core/os/device/remotessh/configuration.go @@ -42,6 +42,9 @@ type Configuration struct { KnownHosts string } +// UnmarshalJSON is used by json.Unmashall, this allows us to set +// up a default configuration, so that unknown parameters have +// sane defaults. func (c *Configuration) UnmarshalJSON(data []byte) error { type configAlias Configuration u, err := user.Current() @@ -64,6 +67,8 @@ func (c *Configuration) UnmarshalJSON(data []byte) error { return nil } +// ReadConfiguration reads a set of configurations from then +// given reader, and returns the configurations to the user. func ReadConfiguration(r io.Reader) ([]Configuration, error) { cfg := []Configuration{} diff --git a/core/os/device/remotessh/device.go b/core/os/device/remotessh/device.go index cd7a1293aa..b4642f1152 100644 --- a/core/os/device/remotessh/device.go +++ b/core/os/device/remotessh/device.go @@ -41,10 +41,6 @@ type Device interface { // PushFile will transfer the local file at sourcePath to the remote // machine at destPath PushFile(ctx context.Context, sourcePath, destPath string) error - // Forward will forward the local port to the remote port - Forward(ctx context.Context, local, device uint16) error - // RemoveForward removes a port forward from local - RemoveForward(ctx context.Context, local uint16) error // MakeTempDir makes a temporary directory, and returns the // path, as well as a function to call to clean it up. MakeTempDir(ctx context.Context) (string, func(ctx context.Context), error) @@ -68,14 +64,6 @@ type binding struct { var _ Device = &binding{} -func (binding) Forward(ctx context.Context, local, device uint16) error { - return nil -} - -func (binding) RemoveForward(ctx context.Context, local uint16) error { - return nil -} - // Devices returns the list of reachable SSH devices. func Devices(ctx context.Context, configuration io.Reader) ([]bind.Device, error) { configurations, err := ReadConfiguration(configuration) @@ -85,7 +73,7 @@ func Devices(ctx context.Context, configuration io.Reader) ([]bind.Device, error devices := make([]bind.Device, 0, len(configurations)) for _, cfg := range configurations { - if device, err := GetConnectedDevice(ctx, cfg); err == nil { + if device, err := getConnectedDevice(ctx, cfg); err == nil { devices = append(devices, device) } } @@ -93,14 +81,17 @@ func Devices(ctx context.Context, configuration io.Reader) ([]bind.Device, error return devices, nil } -func GetSSHAgent() ssh.AuthMethod { +// getSSHAgent returns a connection to a local SSH agent, if one exists. +func getSSHAgent() ssh.AuthMethod { if sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil { return ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers) } return nil } -func GetPrivateKeyAuth(path string) (ssh.AuthMethod, error) { +// This returns an SSH auth for the given private key. +// It will fail if the private key was encrypted. +func getPrivateKeyAuth(path string) (ssh.AuthMethod, error) { bytes, err := ioutil.ReadFile(path) if err != nil { return nil, err @@ -113,15 +104,17 @@ func GetPrivateKeyAuth(path string) (ssh.AuthMethod, error) { return ssh.PublicKeys(signer), nil } -// GetConnectedDevice returns a device that has an option connection. -func GetConnectedDevice(ctx context.Context, c Configuration) (Device, error) { +// getConnectedDevice returns a device that matches the given configuration. +func getConnectedDevice(ctx context.Context, c Configuration) (Device, error) { auths := []ssh.AuthMethod{} - if agent := GetSSHAgent(); agent != nil { + if agent := getSSHAgent(); agent != nil { auths = append(auths, agent) } if c.Keyfile != "" { - if auth, err := GetPrivateKeyAuth(c.Keyfile); err == nil { + // This returns an SSH auth for the given private key. + // It will fail if the private key was encrypted. + if auth, err := getPrivateKeyAuth(c.Keyfile); err == nil { auths = append(auths, auth) } } diff --git a/core/os/process/client.go b/core/os/process/client.go index 04e1bb38d1..64000dd6fb 100644 --- a/core/os/process/client.go +++ b/core/os/process/client.go @@ -127,9 +127,9 @@ type StartOptions struct { WorkingDir string } -// Start runs the application with the given path and options, waits for -// the "Bound on port {port}" string to be printed to stdout, and then returns -// the port number. +// StartOnDevice runs the application on the given remote device, +// with the given path and options, waits for the "Bound on port {port}" string +// to be printed to stdout, and then returns the port number. func StartOnDevice(ctx context.Context, bind bind.Device, name string, opts StartOptions) (int, error) { // Append extra environment variable values errChan := make(chan error, 1) diff --git a/core/vulkan/loader/loader.go b/core/vulkan/loader/loader.go index 38bcbcfc63..8fdadaa4b4 100644 --- a/core/vulkan/loader/loader.go +++ b/core/vulkan/loader/loader.go @@ -31,9 +31,9 @@ import ( "github.com/google/gapid/core/os/shell" ) -// DeviceSetup handles getting files from/to the right location +// deviceReplaySetup handles getting files from/to the right location // for a particular Device -type DeviceReplaySetup interface { +type deviceReplaySetup interface { // MakeTempDir returns a path to a created temporary MakeTempDir(ctx context.Context) (string, func(ctx context.Context), error) @@ -46,6 +46,7 @@ type DeviceReplaySetup interface { FinalizeJSON(ctx context.Context, jsonName string, content string) (string, error) } +// remoteSetup describes moving files to a remote device. type remoteSetup struct { device remotessh.Device abi *device.ABI @@ -74,6 +75,7 @@ func (r *remoteSetup) FinalizeJSON(ctx context.Context, jsonName string, content return jsonName, nil } +// localSetup sets up the JSON for a local device. type localSetup struct { abi *device.ABI } @@ -107,7 +109,7 @@ func (*localSetup) FinalizeJSON(ctx context.Context, jsonName string, content st // clean-up function to be called after the trace completes, and a temporary // filename that can be used to find the port if stdout fails, or an error. func SetupTrace(ctx context.Context, d bind.Device, abi *device.ABI, env *shell.Env) (func(ctx context.Context), string, error) { - var setup DeviceReplaySetup + var setup deviceReplaySetup if dev, ok := d.(remotessh.Device); ok { setup = &remoteSetup{dev, abi} } else { @@ -154,10 +156,10 @@ func SetupTrace(ctx context.Context, d bind.Device, abi *device.ABI, env *shell. return cleanup, f, err } -// SetupReplay sets up the environment for local replay. Returns a clean-up +// SetupReplay sets up the environment for a desktop. Returns a clean-up // function to be called after replay completes, or an error. func SetupReplay(ctx context.Context, d bind.Device, abi *device.ABI, env *shell.Env) (func(ctx context.Context), error) { - var setup DeviceReplaySetup + var setup deviceReplaySetup if dev, ok := d.(remotessh.Device); ok { setup = &remoteSetup{dev, abi} } else { @@ -177,7 +179,7 @@ func SetupReplay(ctx context.Context, d bind.Device, abi *device.ABI, env *shell return cleanup, err } -func findLibraryAndJSON(ctx context.Context, rs DeviceReplaySetup, tempdir string, libType layout.LibraryType) (string, file.Path, error) { +func findLibraryAndJSON(ctx context.Context, rs deviceReplaySetup, tempdir string, libType layout.LibraryType) (string, file.Path, error) { lib, err := rs.InitializeLibrary(ctx, tempdir, libType) if err != nil { return "", file.Path{}, err @@ -190,7 +192,7 @@ func findLibraryAndJSON(ctx context.Context, rs DeviceReplaySetup, tempdir strin return lib, json, nil } -func setupJSON(ctx context.Context, library string, json file.Path, rs DeviceReplaySetup, tempdir string, env *shell.Env) error { +func setupJSON(ctx context.Context, library string, json file.Path, rs deviceReplaySetup, tempdir string, env *shell.Env) error { sourceContent, err := ioutil.ReadFile(json.System()) if err != nil { return err From bd0fe706f0c23acb8d0a94a98e770fef1fdca069 Mon Sep 17 00:00:00 2001 From: Andrew Woloszyn Date: Wed, 23 May 2018 09:47:11 -0400 Subject: [PATCH 05/10] Fixes based on feedback. --- BUILD.bazel | 2 +- cmd/gapis/main.go | 29 ++++---- core/app/layout/layout.go | 24 +++---- core/os/device/deviceinfo/cc/exe/BUILD.bazel | 27 ------- core/os/device/deviceinfo/cc/exe/main.cpp | 76 -------------------- core/os/device/remotessh/BUILD.bazel | 1 + core/os/device/remotessh/commands.go | 68 ++++++++---------- core/os/device/remotessh/device.go | 15 ++-- core/vulkan/loader/loader.go | 12 +++- 9 files changed, 69 insertions(+), 185 deletions(-) delete mode 100644 core/os/device/deviceinfo/cc/exe/BUILD.bazel delete mode 100644 core/os/device/deviceinfo/cc/exe/main.cpp diff --git a/BUILD.bazel b/BUILD.bazel index 676e44ae1a..4e9720e8cd 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -62,7 +62,7 @@ copy_to( "//cmd/gapis", "//cmd/gapit", "//tools/build:build.properties", - "//core/os/device/deviceinfo/cc/exe:device_info", + "//cmd/device-info:device-info", ] + select({ "//tools/build:no-android": [], "//conditions:default": [ diff --git a/cmd/gapis/main.go b/cmd/gapis/main.go index 5bd159e6dc..3b9ae95dd3 100644 --- a/cmd/gapis/main.go +++ b/cmd/gapis/main.go @@ -20,6 +20,7 @@ import ( "io" "os" "path/filepath" + "sync" "time" "google.golang.org/grpc/grpclog" @@ -105,12 +106,11 @@ func run(ctx context.Context) error { r.SetDeviceProperty(ctx, host, client.LaunchArgsKey, text.SplitArgs(*gapirArgStr)) } - numAsyncScans := 0 - scanDone := make(chan bool) + wg := sync.WaitGroup{} if *scanAndroidDevs { - numAsyncScans++ - crash.Go(func() { monitorAndroidDevices(ctx, r, scanDone) }) + wg.Add(1) + crash.Go(func() { monitorAndroidDevices(ctx, r, wg.Done) }) } if *remoteSSHConfig != "" { @@ -118,17 +118,15 @@ func run(ctx context.Context) error { if err != nil { return err } - numAsyncScans++ - crash.Go(func() { getRemoteSSHDevices(ctx, r, f, scanDone) }) + wg.Add(1) + crash.Go(func() { getRemoteSSHDevices(ctx, r, f, wg.Done) }) } deviceScanDone, onDeviceScanDone := task.NewSignal() - go func() { - for i := 0; i < numAsyncScans; i++ { - <-scanDone - } + crash.Go(func() { + wg.Wait() onDeviceScanDone(ctx) - }() + }) return server.Listen(ctx, *rpc, server.Config{ Info: &service.ServerInfo{ @@ -147,10 +145,10 @@ func run(ctx context.Context) error { }) } -func monitorAndroidDevices(ctx context.Context, r *bind.Registry, scanDone chan bool) { +func monitorAndroidDevices(ctx context.Context, r *bind.Registry, scanDone func()) { // Populate the registry with all the existing devices. func() { - defer func() { scanDone <- true }() // Signal that we have a primed registry. + defer scanDone() // Signal that we have a primed registry. if devs, err := adb.Devices(ctx); err == nil { for _, d := range devs { @@ -164,10 +162,10 @@ func monitorAndroidDevices(ctx context.Context, r *bind.Registry, scanDone chan } } -func getRemoteSSHDevices(ctx context.Context, r *bind.Registry, f io.Reader, scanDone chan bool) { +func getRemoteSSHDevices(ctx context.Context, r *bind.Registry, f io.Reader, scanDone func()) { // Populate the registry with all the existing devices. func() { - defer func() { scanDone <- true }() // Signal that we have a primed registry. + defer scanDone() // Signal that we have a primed registry. if devs, err := remotessh.Devices(ctx, f); err == nil { for _, d := range devs { @@ -175,6 +173,7 @@ func getRemoteSSHDevices(ctx context.Context, r *bind.Registry, f io.Reader, sca r.SetDeviceProperty(ctx, d, client.LaunchArgsKey, text.SplitArgs(*gapirArgStr)) } } + }() } diff --git a/core/app/layout/layout.go b/core/app/layout/layout.go index 510c047456..ca638f8d9c 100644 --- a/core/app/layout/layout.go +++ b/core/app/layout/layout.go @@ -128,9 +128,8 @@ var osToDir = map[device.OSKind]string{ func (l pkgLayout) Gapir(ctx context.Context, abi *device.ABI) (file.Path, error) { if hostOS(ctx) == abi.OS { return l.root.Join(withExecutablePlatformSuffix("gapir", hostOS(ctx))), nil - } else { - return l.root.Join(osToDir[abi.OS], withExecutablePlatformSuffix("gapir", abi.OS)), nil } + return l.root.Join(osToDir[abi.OS], withExecutablePlatformSuffix("gapir", abi.OS)), nil } func (l pkgLayout) Gapis(ctx context.Context) (file.Path, error) { @@ -144,9 +143,8 @@ func (l pkgLayout) GapidApk(ctx context.Context, abi *device.ABI) (file.Path, er func (l pkgLayout) Library(ctx context.Context, lib LibraryType, abi *device.ABI) (file.Path, error) { if hostOS(ctx) == abi.OS { return l.root.Join("lib", withLibraryPlatformSuffix(libTypeToName[lib], hostOS(ctx))), nil - } else { - return l.root.Join(osToDir[abi.OS], "lib", withLibraryPlatformSuffix(libTypeToName[lib], abi.OS)), nil } + return l.root.Join(osToDir[abi.OS], "lib", withLibraryPlatformSuffix(libTypeToName[lib], abi.OS)), nil } func (l pkgLayout) Json(ctx context.Context, lib LibraryType) (file.Path, error) { @@ -159,10 +157,9 @@ func (l pkgLayout) GoArgs(ctx context.Context) []string { func (l pkgLayout) DeviceInfo(ctx context.Context, os device.OSKind) (file.Path, error) { if hostOS(ctx) == os { - return l.root.Join(withExecutablePlatformSuffix("device_info", os)), nil - } else { - return l.root.Join(osToDir[os], withExecutablePlatformSuffix("device_info", os)), nil + return l.root.Join(withExecutablePlatformSuffix("device-info", os)), nil } + return l.root.Join(osToDir[os], withExecutablePlatformSuffix("device-info", os)), nil } // NewPkgLayout returns a FileLayout rooted at the given directory with the standard package layout. @@ -277,7 +274,7 @@ func (l *runfilesLayout) GoArgs(ctx context.Context) []string { func (l *runfilesLayout) DeviceInfo(ctx context.Context, os device.OSKind) (file.Path, error) { if hostOS(ctx) == os { - return l.find("core/os/device/deviceinfo/exe/" + withExecutablePlatformSuffix("device_info", os)) + return l.find("core/os/device/deviceinfo/exe/" + withExecutablePlatformSuffix("device-info", os)) } return file.Path{}, ErrCannotFindPackageFiles } @@ -366,9 +363,8 @@ func (l *ZipLayout) Gapit(ctx context.Context) (*zip.File, error) { func (l *ZipLayout) Gapir(ctx context.Context, abi *device.ABI) (*zip.File, error) { if l.os == abi.OS { return l.file(withExecutablePlatformSuffix("gapir", l.os)) - } else { - return l.file(osToDir[abi.OS] + "/" + withExecutablePlatformSuffix("gapir", abi.OS)) } + return l.file(osToDir[abi.OS] + "/" + withExecutablePlatformSuffix("gapir", abi.OS)) } // Gapis returns the path to the gapis binary in this layout. @@ -385,9 +381,8 @@ func (l *ZipLayout) GapidApk(ctx context.Context, abi *device.ABI) (*zip.File, e func (l *ZipLayout) Library(ctx context.Context, lib LibraryType, abi *device.ABI) (*zip.File, error) { if l.os == abi.OS { return l.file("lib/" + withLibraryPlatformSuffix(libTypeToName[lib], l.os)) - } else { - return l.file(osToDir[abi.OS] + "lib/" + withLibraryPlatformSuffix(libTypeToName[lib], abi.OS)) } + return l.file(osToDir[abi.OS] + "lib/" + withLibraryPlatformSuffix(libTypeToName[lib], abi.OS)) } // Json returns the path to the Vulkan layer JSON definition for the given library. @@ -398,8 +393,7 @@ func (l *ZipLayout) Json(ctx context.Context, lib LibraryType) (*zip.File, error // DeviceInfo returns the device info executable for the given ABI. func (l *ZipLayout) DeviceInfo(ctx context.Context, os device.OSKind) (*zip.File, error) { if l.os == os { - return l.file(withExecutablePlatformSuffix("device_info", os)) - } else { - return l.file(osToDir[os] + "/" + withExecutablePlatformSuffix("device_info", os)) + return l.file(withExecutablePlatformSuffix("device-info", os)) } + return l.file(osToDir[os] + "/" + withExecutablePlatformSuffix("device-info", os)) } diff --git a/core/os/device/deviceinfo/cc/exe/BUILD.bazel b/core/os/device/deviceinfo/cc/exe/BUILD.bazel deleted file mode 100644 index ec31ab2baa..0000000000 --- a/core/os/device/deviceinfo/cc/exe/BUILD.bazel +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (C) 2018 Google Inc. -# -# 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. - -load("//tools/build:rules.bzl", "cc_copts", "cc_stripped_binary", "android_dynamic_library", "android_native_app_glue") - -cc_stripped_binary( - name = "device_info", - srcs = ["main.cpp"], - copts = cc_copts(), - visibility = ["//visibility:public"], - deps = [ - "//core/os/device/deviceinfo/cc:cc", - "@com_google_protobuf//:protobuf", - "//core/os/device:device_cc_proto", - ], -) diff --git a/core/os/device/deviceinfo/cc/exe/main.cpp b/core/os/device/deviceinfo/cc/exe/main.cpp deleted file mode 100644 index 594640df2f..0000000000 --- a/core/os/device/deviceinfo/cc/exe/main.cpp +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2018 Google Inc. - * - * 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. - */ - -#include -#include -#include -#include - -#include "core/os/device/deviceinfo/cc/instance.h" -#include "core/os/device/device.pb.h" -#include - -void print_help() { - std::cout << "Usage: device_info [--binary]" << std::endl; - std::cout << "Output information about the current device." << std::endl; - std::cout << " --binary Output a binary protobuf instead of json" << std::endl; -} - -int main(int argc, char const *argv[]) -{ - bool output_binary = false; - for (size_t i = 1; i < argc; ++i) { - if (strcmp(argv[i], "--help") == 0 || - strcmp(argv[i], "-help") == 0 || - strcmp(argv[i], "-h") == 0) { - print_help(); - return 0; - } else if (strcmp(argv[i], "--binary") == 0) { - output_binary = true; - } else { - print_help(); - return -1; - } - } - - device_instance instance = get_device_instance(nullptr); - if (output_binary) { -#if _WIN32 - _setmode(_fileno(stdout), _O_BINARY); -#endif - std::cout << std::string(reinterpret_cast(instance.data), instance.size); - } else { - device::Instance device_inst; - if (!device_inst.ParseFromArray(instance.data, instance.size)) { - std::cerr << "Internal error." << std::endl; - free_device_instance(instance); - return -1; - } - std::string output; - google::protobuf::util::JsonPrintOptions options; - options.add_whitespace = true; - if (google::protobuf::util::Status::OK != google::protobuf::util::MessageToJsonString(device_inst, &output, options)) { - std::cerr << "Internal error: Could not convert to json" << std::endl; - free_device_instance(instance); - return -1; - } - std::cout << output; - } - - free_device_instance(instance); - - return 0; -} diff --git a/core/os/device/remotessh/BUILD.bazel b/core/os/device/remotessh/BUILD.bazel index 5cd2ae717d..a9ab59c383 100644 --- a/core/os/device/remotessh/BUILD.bazel +++ b/core/os/device/remotessh/BUILD.bazel @@ -28,6 +28,7 @@ go_library( "//core/log:go_default_library", "//core/os/device:go_default_library", "//core/os/shell:go_default_library", + "//core/app/crash:go_default_library", "//core/app/layout:go_default_library", "@org_golang_x_crypto//ssh:go_default_library", "@org_golang_x_crypto//ssh/agent:go_default_library", diff --git a/core/os/device/remotessh/commands.go b/core/os/device/remotessh/commands.go index d3af0282c9..d26baff74a 100644 --- a/core/os/device/remotessh/commands.go +++ b/core/os/device/remotessh/commands.go @@ -21,18 +21,19 @@ import ( "net" "os" "strings" + "sync" "github.com/google/gapid/core/log" "github.com/google/gapid/core/os/device" "github.com/google/gapid/core/os/shell" + "github.com/google/gapid/core/app/crash" "golang.org/x/crypto/ssh" ) -// Process is the interface to a running process, as started by a Target. +// remoteProcess is the interface to a running process, as started by a Target. type remoteProcess struct { session *ssh.Session - stdoutDone chan error - stderrDone chan error + wg sync.WaitGroup } func (r *remoteProcess) Kill() error { @@ -41,8 +42,7 @@ func (r *remoteProcess) Kill() error { func (r *remoteProcess) Wait(ctx context.Context) error { ret := r.session.Wait() - <-r.stdoutDone - <-r.stderrDone + r.wg.Wait() return ret } @@ -58,8 +58,7 @@ func (t sshShellTarget) Start(cmd shell.Cmd) (shell.Process, error) { } p := &remoteProcess{ session: session, - stdoutDone: make(chan error), - stderrDone: make(chan error), + wg: sync.WaitGroup{}, } if cmd.Stdin != nil { @@ -78,23 +77,18 @@ func (t sshShellTarget) Start(cmd shell.Cmd) (shell.Process, error) { if err != nil { return nil, err } - go func() { + p.wg.Add(1) + crash.Go(func() { b := make([]byte, 1024) for { n, err := stdout.Read(b) cmd.Stdout.Write(b[:n]) if err != nil { - if err == io.EOF { - p.stdoutDone <- nil - } else { - p.stdoutDone <- err - } + p.wg.Done() break } } - }() - } else { - p.stdoutDone <- nil + }) } if cmd.Stderr != nil { @@ -102,23 +96,18 @@ func (t sshShellTarget) Start(cmd shell.Cmd) (shell.Process, error) { if err != nil { return nil, err } - go func() { + p.wg.Add(1) + crash.Go(func() { b := make([]byte, 1024) for { n, err := stderr.Read(b) cmd.Stderr.Write(b[:n]) if err != nil { - if err == io.EOF { - p.stderrDone <- nil - } else { - p.stderrDone <- err - } + p.wg.Done() break } } - }() - } else { - p.stderrDone <- nil + }) } prefix := "" @@ -176,7 +165,7 @@ func (b binding) MakeTempDir(ctx context.Context) (string, func(ctx context.Cont case device.Windows: return b.createWindowsTempDirectory(ctx) default: - panic("We should never end up here") + panic(fmt.Errorf("Unsupported OS %v", b.os)) } } @@ -211,26 +200,25 @@ func (b binding) doTunnel(ctx context.Context, local net.Conn, remotePort int) e return err } - closer := make(chan bool) - + wg := sync.WaitGroup{} + copy := func(writer io.Writer, reader io.Reader) { _, err := io.Copy(writer, reader) if err != nil { log.E(ctx, "Copy Error %s", err) } - closer <- true + wg.Done() } - go copy(local, remote) - go copy(remote, local) - go func() { + wg.Add(2) + crash.Go( func() { copy(local, remote)} ) + crash.Go( func() { copy(remote, local)} ) + + crash.Go( func() { defer local.Close() defer remote.Close() - // When one direction of the communication has - // closed, close the entire connection - <-closer - <-closer - }() + wg.Wait() + }) return nil } @@ -241,16 +229,16 @@ func (b binding) ForwardPort(ctx context.Context, remotePort int) (int, error) { if err != nil { return 0, err } - go func() { + crash.Go( func() { defer listener.Close() for { local, err := listener.Accept() if err != nil { return } - go b.doTunnel(ctx, local, remotePort) + b.doTunnel(ctx, local, remotePort) } - }() + }) return listener.Addr().(*net.TCPAddr).Port, nil } diff --git a/core/os/device/remotessh/device.go b/core/os/device/remotessh/device.go index b4642f1152..457bb459df 100644 --- a/core/os/device/remotessh/device.go +++ b/core/os/device/remotessh/device.go @@ -29,7 +29,6 @@ import ( "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" "golang.org/x/crypto/ssh/knownhosts" - "github.com/google/gapid/core/os/device" "github.com/google/gapid/core/os/device/bind" ) @@ -70,6 +69,7 @@ func Devices(ctx context.Context, configuration io.Reader) ([]bind.Device, error if err != nil { return nil, err } + devices := make([]bind.Device, 0, len(configurations)) for _, cfg := range configurations { @@ -122,7 +122,7 @@ func getConnectedDevice(ctx context.Context, c Configuration) (Device, error) { if len(auths) == 0 { return nil, fmt.Errorf("No valid authentication method for SSH connection %s", c.Name) } - + hosts, err := knownhosts.New(c.KnownHosts) if err != nil { return nil, fmt.Errorf("Could not read known hosts %v", err) @@ -133,7 +133,7 @@ func getConnectedDevice(ctx context.Context, c Configuration) (Device, error) { Auth: auths, HostKeyCallback: hosts, } - + connection, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", c.Host, c.Port), sshConfig) if err != nil { return nil, err @@ -152,7 +152,7 @@ func getConnectedDevice(ctx context.Context, c Configuration) (Device, error) { } kind := device.UnknownOS - + // Try to get the OS string for Mac/Linux if osName, err := b.Shell("uname", "-a").Call(ctx); err == nil { if strings.Contains(osName, "Darwin") { @@ -161,7 +161,7 @@ func getConnectedDevice(ctx context.Context, c Configuration) (Device, error) { kind = device.Linux } } - + if kind == device.UnknownOS { // Try to get the OS string for Windows if osName, err := b.Shell("ver").Call(ctx); err == nil { @@ -175,7 +175,6 @@ func getConnectedDevice(ctx context.Context, c Configuration) (Device, error) { return nil, fmt.Errorf("Could not determine unix type") } b.os = kind - dir, cleanup, err := b.MakeTempDir(ctx) if err != nil { return nil, err @@ -187,11 +186,11 @@ func getConnectedDevice(ctx context.Context, c Configuration) (Device, error) { return nil, err } - if err = b.PushFile(ctx, localDeviceInfo.System(), dir+"/device_info"); err != nil { + if err = b.PushFile(ctx, localDeviceInfo.System(), dir+"/device-info"); err != nil { return nil, err } - devInfo, err := b.Shell("./device_info").In(dir).Call(ctx) + devInfo, err := b.Shell("./device-info").In(dir).Call(ctx) if err != nil { return nil, err diff --git a/core/vulkan/loader/loader.go b/core/vulkan/loader/loader.go index 8fdadaa4b4..9e67378323 100644 --- a/core/vulkan/loader/loader.go +++ b/core/vulkan/loader/loader.go @@ -34,7 +34,8 @@ import ( // deviceReplaySetup handles getting files from/to the right location // for a particular Device type deviceReplaySetup interface { - // MakeTempDir returns a path to a created temporary + // MakeTempDir returns a path to a created temporary. The returned function + // can be called to clean up the temporary directory. MakeTempDir(ctx context.Context) (string, func(ctx context.Context), error) // InitializeLibrary takes a library, and if necessary copies it @@ -172,11 +173,16 @@ func SetupReplay(ctx context.Context, d bind.Device, abi *device.ABI, env *shell lib, json, err := findLibraryAndJSON(ctx, setup, tempdir, layout.LibVirtualSwapChain) if err != nil { - return func(ctx context.Context) {}, err + cleanup(ctx) + return nil, err } err = setupJSON(ctx, lib, json, setup, tempdir, env) - return cleanup, err + if err != nil { + cleanup(ctx) + return nil, err + } + return cleanup, nil } func findLibraryAndJSON(ctx context.Context, rs deviceReplaySetup, tempdir string, libType layout.LibraryType) (string, file.Path, error) { From 8d8cbb24d49c97c0f6ce918b389bf48fe0029d75 Mon Sep 17 00:00:00 2001 From: Andrew Woloszyn Date: Wed, 23 May 2018 11:06:53 -0400 Subject: [PATCH 06/10] More fixes based on comments. --- cmd/device-info/BUILD.bazel | 27 ++++++++++ cmd/device-info/main.cpp | 76 ++++++++++++++++++++++++++++ cmd/gapis/main.go | 1 - core/os/device/remotessh/commands.go | 54 +++++++++----------- 4 files changed, 126 insertions(+), 32 deletions(-) create mode 100644 cmd/device-info/BUILD.bazel create mode 100644 cmd/device-info/main.cpp diff --git a/cmd/device-info/BUILD.bazel b/cmd/device-info/BUILD.bazel new file mode 100644 index 0000000000..1cb1502cc5 --- /dev/null +++ b/cmd/device-info/BUILD.bazel @@ -0,0 +1,27 @@ +# Copyright (C) 2018 Google Inc. +# +# 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. + +load("//tools/build:rules.bzl", "cc_copts", "cc_stripped_binary") + +cc_stripped_binary( + name = "device-info", + srcs = ["main.cpp"], + copts = cc_copts(), + visibility = ["//visibility:public"], + deps = [ + "//core/os/device/deviceinfo/cc:cc", + "@com_google_protobuf//:protobuf", + "//core/os/device:device_cc_proto", + ], +) diff --git a/cmd/device-info/main.cpp b/cmd/device-info/main.cpp new file mode 100644 index 0000000000..107ca16c8f --- /dev/null +++ b/cmd/device-info/main.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2018 Google Inc. + * + * 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. + */ + +#include +#include +#include +#include + +#include "core/os/device/deviceinfo/cc/instance.h" +#include "core/os/device/device.pb.h" +#include + +void print_help() { + std::cout << "Usage: device-info [--binary]" << std::endl; + std::cout << "Output information about the current device." << std::endl; + std::cout << " --binary Output a binary protobuf instead of json" << std::endl; +} + +int main(int argc, char const *argv[]) +{ + bool output_binary = false; + for (size_t i = 1; i < argc; ++i) { + if (strcmp(argv[i], "--help") == 0 || + strcmp(argv[i], "-help") == 0 || + strcmp(argv[i], "-h") == 0) { + print_help(); + return 0; + } else if (strcmp(argv[i], "--binary") == 0) { + output_binary = true; + } else { + print_help(); + return -1; + } + } + + device_instance instance = get_device_instance(nullptr); + if (output_binary) { +#if _WIN32 + _setmode(_fileno(stdout), _O_BINARY); +#endif + std::cout << std::string(reinterpret_cast(instance.data), instance.size); + } else { + device::Instance device_inst; + if (!device_inst.ParseFromArray(instance.data, instance.size)) { + std::cerr << "Internal error." << std::endl; + free_device_instance(instance); + return -1; + } + std::string output; + google::protobuf::util::JsonPrintOptions options; + options.add_whitespace = true; + if (google::protobuf::util::Status::OK != google::protobuf::util::MessageToJsonString(device_inst, &output, options)) { + std::cerr << "Internal error: Could not convert to json" << std::endl; + free_device_instance(instance); + return -1; + } + std::cout << output; + } + + free_device_instance(instance); + + return 0; +} diff --git a/cmd/gapis/main.go b/cmd/gapis/main.go index 3b9ae95dd3..bf2903f59f 100644 --- a/cmd/gapis/main.go +++ b/cmd/gapis/main.go @@ -173,7 +173,6 @@ func getRemoteSSHDevices(ctx context.Context, r *bind.Registry, f io.Reader, sca r.SetDeviceProperty(ctx, d, client.LaunchArgsKey, text.SplitArgs(*gapirArgStr)) } } - }() } diff --git a/core/os/device/remotessh/commands.go b/core/os/device/remotessh/commands.go index d26baff74a..c3635ee7ff 100644 --- a/core/os/device/remotessh/commands.go +++ b/core/os/device/remotessh/commands.go @@ -23,17 +23,17 @@ import ( "strings" "sync" + "github.com/google/gapid/core/app/crash" "github.com/google/gapid/core/log" "github.com/google/gapid/core/os/device" "github.com/google/gapid/core/os/shell" - "github.com/google/gapid/core/app/crash" "golang.org/x/crypto/ssh" ) // remoteProcess is the interface to a running process, as started by a Target. type remoteProcess struct { - session *ssh.Session - wg sync.WaitGroup + session *ssh.Session + wg sync.WaitGroup } func (r *remoteProcess) Kill() error { @@ -50,6 +50,12 @@ var _ shell.Process = (*remoteProcess)(nil) type sshShellTarget struct{ b *binding } +type writerWrapper struct{ w io.Writer } + +func (w *writerWrapper) Write(p []byte) (n int, err error) { + return w.w.Write(p) +} + // Start starts the given command in the remote shell. func (t sshShellTarget) Start(cmd shell.Cmd) (shell.Process, error) { session, err := t.b.connection.NewSession() @@ -57,8 +63,8 @@ func (t sshShellTarget) Start(cmd shell.Cmd) (shell.Process, error) { return nil, err } p := &remoteProcess{ - session: session, - wg: sync.WaitGroup{}, + session: session, + wg: sync.WaitGroup{}, } if cmd.Stdin != nil { @@ -66,10 +72,10 @@ func (t sshShellTarget) Start(cmd shell.Cmd) (shell.Process, error) { if err != nil { return nil, err } - go func() { + crash.Go(func() { defer stdin.Close() io.Copy(stdin, cmd.Stdin) - }() + }) } if cmd.Stdout != nil { @@ -79,15 +85,8 @@ func (t sshShellTarget) Start(cmd shell.Cmd) (shell.Process, error) { } p.wg.Add(1) crash.Go(func() { - b := make([]byte, 1024) - for { - n, err := stdout.Read(b) - cmd.Stdout.Write(b[:n]) - if err != nil { - p.wg.Done() - break - } - } + io.Copy(&writerWrapper{cmd.Stdout}, stdout) + p.wg.Done() }) } @@ -98,15 +97,8 @@ func (t sshShellTarget) Start(cmd shell.Cmd) (shell.Process, error) { } p.wg.Add(1) crash.Go(func() { - b := make([]byte, 1024) - for { - n, err := stderr.Read(b) - cmd.Stderr.Write(b[:n]) - if err != nil { - p.wg.Done() - break - } - } + io.Copy(&writerWrapper{cmd.Stderr}, stderr) + p.wg.Done() }) } @@ -201,7 +193,7 @@ func (b binding) doTunnel(ctx context.Context, local net.Conn, remotePort int) e } wg := sync.WaitGroup{} - + copy := func(writer io.Writer, reader io.Reader) { _, err := io.Copy(writer, reader) if err != nil { @@ -211,10 +203,10 @@ func (b binding) doTunnel(ctx context.Context, local net.Conn, remotePort int) e } wg.Add(2) - crash.Go( func() { copy(local, remote)} ) - crash.Go( func() { copy(remote, local)} ) - - crash.Go( func() { + crash.Go(func() { copy(local, remote) }) + crash.Go(func() { copy(remote, local) }) + + crash.Go(func() { defer local.Close() defer remote.Close() wg.Wait() @@ -229,7 +221,7 @@ func (b binding) ForwardPort(ctx context.Context, remotePort int) (int, error) { if err != nil { return 0, err } - crash.Go( func() { + crash.Go(func() { defer listener.Close() for { local, err := listener.Accept() From d4467ba5102dfb5deb9827eb56257701b96d635d Mon Sep 17 00:00:00 2001 From: Andrew Woloszyn Date: Wed, 23 May 2018 11:25:07 -0400 Subject: [PATCH 07/10] Fixed a bug with the command output. We were writing both stdout and stderr to the same buffer in a very thread-unsafe way. --- core/os/device/remotessh/commands.go | 10 ++-------- core/os/shell/command.go | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/core/os/device/remotessh/commands.go b/core/os/device/remotessh/commands.go index c3635ee7ff..151361c519 100644 --- a/core/os/device/remotessh/commands.go +++ b/core/os/device/remotessh/commands.go @@ -50,12 +50,6 @@ var _ shell.Process = (*remoteProcess)(nil) type sshShellTarget struct{ b *binding } -type writerWrapper struct{ w io.Writer } - -func (w *writerWrapper) Write(p []byte) (n int, err error) { - return w.w.Write(p) -} - // Start starts the given command in the remote shell. func (t sshShellTarget) Start(cmd shell.Cmd) (shell.Process, error) { session, err := t.b.connection.NewSession() @@ -85,7 +79,7 @@ func (t sshShellTarget) Start(cmd shell.Cmd) (shell.Process, error) { } p.wg.Add(1) crash.Go(func() { - io.Copy(&writerWrapper{cmd.Stdout}, stdout) + io.Copy(cmd.Stdout, stdout) p.wg.Done() }) } @@ -97,7 +91,7 @@ func (t sshShellTarget) Start(cmd shell.Cmd) (shell.Process, error) { } p.wg.Add(1) crash.Go(func() { - io.Copy(&writerWrapper{cmd.Stderr}, stderr) + io.Copy(cmd.Stderr, stderr) p.wg.Done() }) } diff --git a/core/os/shell/command.go b/core/os/shell/command.go index ab11e18bf0..3ce450c205 100644 --- a/core/os/shell/command.go +++ b/core/os/shell/command.go @@ -22,6 +22,7 @@ import ( "os" "path/filepath" "strings" + "sync" "github.com/google/gapid/core/log" ) @@ -149,13 +150,24 @@ func (cmd Cmd) Run(ctx context.Context) error { return nil } +type muxedBuffer struct { + buf bytes.Buffer + mutex sync.Mutex +} + +func (m *muxedBuffer) Write(b []byte) (int, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + return m.buf.Write(b) +} + // Call executes the command, capturing its output. // This is a helper for the common case where you want to run a command, capture all its output into a string and // see if it succeeded. func (cmd Cmd) Call(ctx context.Context) (string, error) { - buf := &bytes.Buffer{} + buf := &muxedBuffer{} err := cmd.Capture(buf, buf).Run(ctx) - output := strings.TrimSpace(buf.String()) + output := strings.TrimSpace(buf.buf.String()) return output, err } From f999dd90d7ebcdbf751c0a4140195649411e3150 Mon Sep 17 00:00:00 2001 From: Andrew Woloszyn Date: Wed, 23 May 2018 12:30:51 -0400 Subject: [PATCH 08/10] More updates for comments --- cmd/gapis/main.go | 14 ++++----- core/app/layout/layout.go | 4 +-- core/os/device/remotessh/BUILD.bazel | 3 +- core/os/device/remotessh/commands.go | 23 +++++++++------ core/os/device/remotessh/device.go | 16 +++++------ core/vulkan/loader/loader.go | 43 +++++++++++++++------------- 6 files changed, 55 insertions(+), 48 deletions(-) diff --git a/cmd/gapis/main.go b/cmd/gapis/main.go index bf2903f59f..6f57b704f1 100644 --- a/cmd/gapis/main.go +++ b/cmd/gapis/main.go @@ -164,16 +164,14 @@ func monitorAndroidDevices(ctx context.Context, r *bind.Registry, scanDone func( func getRemoteSSHDevices(ctx context.Context, r *bind.Registry, f io.Reader, scanDone func()) { // Populate the registry with all the existing devices. - func() { - defer scanDone() // Signal that we have a primed registry. + defer scanDone() // Signal that we have a primed registry. - if devs, err := remotessh.Devices(ctx, f); err == nil { - for _, d := range devs { - r.AddDevice(ctx, d) - r.SetDeviceProperty(ctx, d, client.LaunchArgsKey, text.SplitArgs(*gapirArgStr)) - } + if devs, err := remotessh.Devices(ctx, f); err == nil { + for _, d := range devs { + r.AddDevice(ctx, d) + r.SetDeviceProperty(ctx, d, client.LaunchArgsKey, text.SplitArgs(*gapirArgStr)) } - }() + } } func loadStrings(ctx context.Context) []*stringtable.StringTable { diff --git a/core/app/layout/layout.go b/core/app/layout/layout.go index ca638f8d9c..092de85d10 100644 --- a/core/app/layout/layout.go +++ b/core/app/layout/layout.go @@ -90,8 +90,8 @@ func withLibraryPlatformSuffix(lib string, os device.OSKind) string { } } -// GetLibraryName returns the filename of the given Library. -func GetLibraryName(lib LibraryType, abi *device.ABI) string { +// LibraryName returns the filename of the given Library. +func LibraryName(lib LibraryType, abi *device.ABI) string { return withLibraryPlatformSuffix(libTypeToName[lib], abi.OS) } diff --git a/core/os/device/remotessh/BUILD.bazel b/core/os/device/remotessh/BUILD.bazel index a9ab59c383..b0d3528268 100644 --- a/core/os/device/remotessh/BUILD.bazel +++ b/core/os/device/remotessh/BUILD.bazel @@ -24,9 +24,10 @@ go_library( importpath = "github.com/google/gapid/core/os/device/remotessh", visibility = ["//visibility:public"], deps = [ - "//core/os/device/bind:go_default_library", "//core/log:go_default_library", + "//core/event/task:go_default_library", "//core/os/device:go_default_library", + "//core/os/device/bind:go_default_library", "//core/os/shell:go_default_library", "//core/app/crash:go_default_library", "//core/app/layout:go_default_library", diff --git a/core/os/device/remotessh/commands.go b/core/os/device/remotessh/commands.go index 151361c519..7932eeb708 100644 --- a/core/os/device/remotessh/commands.go +++ b/core/os/device/remotessh/commands.go @@ -24,6 +24,7 @@ import ( "sync" "github.com/google/gapid/core/app/crash" + "github.com/google/gapid/core/event/task" "github.com/google/gapid/core/log" "github.com/google/gapid/core/os/device" "github.com/google/gapid/core/os/shell" @@ -137,16 +138,14 @@ func (b binding) createPosixTempDirectory(ctx context.Context) (string, func(con } func (b binding) createWindowsTempDirectory(ctx context.Context) (string, func(ctx context.Context), error) { - return "", nil, fmt.Errorf("Not yet supported, windows remote targets") + return "", nil, fmt.Errorf("Windows remote targets are not yet supported.") } // MakeTempDir creates a temporary directory on the remote machine. It returns the // full path, and a function that can be called to clean up the directory. func (b binding) MakeTempDir(ctx context.Context) (string, func(ctx context.Context), error) { switch b.os { - case device.Linux: - fallthrough - case device.OSX: + case device.Linux, device.OSX: return b.createPosixTempDirectory(ctx) case device.Windows: return b.createWindowsTempDirectory(ctx) @@ -157,8 +156,9 @@ func (b binding) MakeTempDir(ctx context.Context) (string, func(ctx context.Cont // WriteFile moves the contents of io.Reader into the given file on the remote machine. // The file is given the mode as described by the unix filemode string. -func (b binding) WriteFile(ctx context.Context, contents io.Reader, mode string, destPath string) error { - _, err := b.Shell("cat", ">", destPath, "; chmod ", mode, " ", destPath).Read(contents).Call(ctx) +func (b binding) WriteFile(ctx context.Context, contents io.Reader, mode os.FileMode, destPath string) error { + perm := fmt.Sprintf("%4o", mode.Perm()) + _, err := b.Shell("cat", ">", destPath, "; chmod ", perm, " ", destPath).Read(contents).Call(ctx) return err } @@ -173,9 +173,8 @@ func (b binding) PushFile(ctx context.Context, source, dest string) error { if err != nil { return err } - perm := fmt.Sprintf("%4o", permission.Mode().Perm()) - return b.WriteFile(ctx, infile, perm, dest) + return b.WriteFile(ctx, infile, permission.Mode(), dest) } // doTunnel tunnels a single connection through the SSH connection. @@ -215,6 +214,10 @@ func (b binding) ForwardPort(ctx context.Context, remotePort int) (int, error) { if err != nil { return 0, err } + crash.Go(func() { + <-task.ShouldStop(ctx) + listener.Close() + }) crash.Go(func() { defer listener.Close() for { @@ -222,7 +225,9 @@ func (b binding) ForwardPort(ctx context.Context, remotePort int) (int, error) { if err != nil { return } - b.doTunnel(ctx, local, remotePort) + if err = b.doTunnel(ctx, local, remotePort); err != nil { + break + } } }) diff --git a/core/os/device/remotessh/device.go b/core/os/device/remotessh/device.go index 457bb459df..1e5be58f74 100644 --- a/core/os/device/remotessh/device.go +++ b/core/os/device/remotessh/device.go @@ -26,11 +26,11 @@ import ( "github.com/golang/protobuf/jsonpb" "github.com/google/gapid/core/app/layout" + "github.com/google/gapid/core/os/device" + "github.com/google/gapid/core/os/device/bind" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" "golang.org/x/crypto/ssh/knownhosts" - "github.com/google/gapid/core/os/device" - "github.com/google/gapid/core/os/device/bind" ) // Device extends the bind.Device interface with capabilities specific to @@ -44,7 +44,7 @@ type Device interface { // path, as well as a function to call to clean it up. MakeTempDir(ctx context.Context) (string, func(ctx context.Context), error) // WriteFile writes the given file into the given location on the remote device - WriteFile(ctx context.Context, contents io.Reader, mode string, destPath string) error + WriteFile(ctx context.Context, contents io.Reader, mode os.FileMode, destPath string) error // ForwardPort forwards the remote port. It automatically selects an open // local port. ForwardPort(ctx context.Context, remoteport int) (int, error) @@ -69,7 +69,7 @@ func Devices(ctx context.Context, configuration io.Reader) ([]bind.Device, error if err != nil { return nil, err } - + devices := make([]bind.Device, 0, len(configurations)) for _, cfg := range configurations { @@ -122,7 +122,7 @@ func getConnectedDevice(ctx context.Context, c Configuration) (Device, error) { if len(auths) == 0 { return nil, fmt.Errorf("No valid authentication method for SSH connection %s", c.Name) } - + hosts, err := knownhosts.New(c.KnownHosts) if err != nil { return nil, fmt.Errorf("Could not read known hosts %v", err) @@ -133,7 +133,7 @@ func getConnectedDevice(ctx context.Context, c Configuration) (Device, error) { Auth: auths, HostKeyCallback: hosts, } - + connection, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", c.Host, c.Port), sshConfig) if err != nil { return nil, err @@ -152,7 +152,7 @@ func getConnectedDevice(ctx context.Context, c Configuration) (Device, error) { } kind := device.UnknownOS - + // Try to get the OS string for Mac/Linux if osName, err := b.Shell("uname", "-a").Call(ctx); err == nil { if strings.Contains(osName, "Darwin") { @@ -161,7 +161,7 @@ func getConnectedDevice(ctx context.Context, c Configuration) (Device, error) { kind = device.Linux } } - + if kind == device.UnknownOS { // Try to get the OS string for Windows if osName, err := b.Shell("ver").Call(ctx); err == nil { diff --git a/core/vulkan/loader/loader.go b/core/vulkan/loader/loader.go index 9e67378323..3851081220 100644 --- a/core/vulkan/loader/loader.go +++ b/core/vulkan/loader/loader.go @@ -34,17 +34,17 @@ import ( // deviceReplaySetup handles getting files from/to the right location // for a particular Device type deviceReplaySetup interface { - // MakeTempDir returns a path to a created temporary. The returned function + // makeTempDir returns a path to a created temporary. The returned function // can be called to clean up the temporary directory. - MakeTempDir(ctx context.Context) (string, func(ctx context.Context), error) + makeTempDir(ctx context.Context) (string, func(ctx context.Context), error) - // InitializeLibrary takes a library, and if necessary copies it + // initializeLibrary takes a library, and if necessary copies it // into the given temporary directory. It returns the library // location if necessary. - InitializeLibrary(ctx context.Context, tempdir string, library layout.LibraryType) (string, error) + initializeLibrary(ctx context.Context, tempdir string, library layout.LibraryType) (string, error) - // FinalizeJSON puts the given JSON content in the given file - FinalizeJSON(ctx context.Context, jsonName string, content string) (string, error) + // finalizeJSON puts the given JSON content in the given file + finalizeJSON(ctx context.Context, jsonName string, content string) (string, error) } // remoteSetup describes moving files to a remote device. @@ -53,24 +53,24 @@ type remoteSetup struct { abi *device.ABI } -func (r *remoteSetup) MakeTempDir(ctx context.Context) (string, func(ctx context.Context), error) { +func (r *remoteSetup) makeTempDir(ctx context.Context) (string, func(ctx context.Context), error) { return r.device.MakeTempDir(ctx) } -func (r *remoteSetup) InitializeLibrary(ctx context.Context, tempdir string, library layout.LibraryType) (string, error) { +func (r *remoteSetup) initializeLibrary(ctx context.Context, tempdir string, library layout.LibraryType) (string, error) { lib, err := layout.Library(ctx, library, r.abi) if err != nil { return "", err } - libName := layout.GetLibraryName(library, r.abi) + libName := layout.LibraryName(library, r.abi) if err := r.device.PushFile(ctx, lib.System(), tempdir+"/"+libName); err != nil { return "", err } return tempdir + "/" + libName, nil } -func (r *remoteSetup) FinalizeJSON(ctx context.Context, jsonName string, content string) (string, error) { - if err := r.device.WriteFile(ctx, bytes.NewReader([]byte(content)), "0644", jsonName); err != nil { +func (r *remoteSetup) finalizeJSON(ctx context.Context, jsonName string, content string) (string, error) { + if err := r.device.WriteFile(ctx, bytes.NewReader([]byte(content)), os.FileMode(0644), jsonName); err != nil { return "", err } return jsonName, nil @@ -81,7 +81,7 @@ type localSetup struct { abi *device.ABI } -func (*localSetup) MakeTempDir(ctx context.Context) (string, func(ctx context.Context), error) { +func (*localSetup) makeTempDir(ctx context.Context) (string, func(ctx context.Context), error) { tempdir, err := ioutil.TempDir("", "temp") if err != nil { return "", nil, err @@ -91,7 +91,7 @@ func (*localSetup) MakeTempDir(ctx context.Context) (string, func(ctx context.Co }, nil } -func (l *localSetup) InitializeLibrary(ctx context.Context, tempdir string, library layout.LibraryType) (string, error) { +func (l *localSetup) initializeLibrary(ctx context.Context, tempdir string, library layout.LibraryType) (string, error) { lib, err := layout.Library(ctx, library, l.abi) if err != nil { return "", err @@ -99,7 +99,7 @@ func (l *localSetup) InitializeLibrary(ctx context.Context, tempdir string, libr return lib.System(), nil } -func (*localSetup) FinalizeJSON(ctx context.Context, jsonName string, content string) (string, error) { +func (*localSetup) finalizeJSON(ctx context.Context, jsonName string, content string) (string, error) { if err := ioutil.WriteFile(jsonName, []byte(content), 0644); err != nil { return "", err } @@ -116,7 +116,7 @@ func SetupTrace(ctx context.Context, d bind.Device, abi *device.ABI, env *shell. } else { setup = &localSetup{abi} } - tempdir, cleanup, err := setup.MakeTempDir(ctx) + tempdir, cleanup, err := setup.makeTempDir(ctx) if err != nil { return func(ctx context.Context) {}, "", err } @@ -166,7 +166,7 @@ func SetupReplay(ctx context.Context, d bind.Device, abi *device.ABI, env *shell } else { setup = &localSetup{abi} } - tempdir, cleanup, err := setup.MakeTempDir(ctx) + tempdir, cleanup, err := setup.makeTempDir(ctx) if err != nil { return nil, err } @@ -177,16 +177,19 @@ func SetupReplay(ctx context.Context, d bind.Device, abi *device.ABI, env *shell return nil, err } - err = setupJSON(ctx, lib, json, setup, tempdir, env) - if err != nil { + if err = setupJSON(ctx, lib, json, setup, tempdir, env); err != nil { cleanup(ctx) return nil, err } + return cleanup, nil } +// findLibraryAndJSON moves the library to the correct location (either locally or remotely) and returns +// a string representing the location, it also returns the file.Path of the associated JSON file for this +// library. func findLibraryAndJSON(ctx context.Context, rs deviceReplaySetup, tempdir string, libType layout.LibraryType) (string, file.Path, error) { - lib, err := rs.InitializeLibrary(ctx, tempdir, libType) + lib, err := rs.initializeLibrary(ctx, tempdir, libType) if err != nil { return "", file.Path{}, err } @@ -207,7 +210,7 @@ func setupJSON(ctx context.Context, library string, json file.Path, rs deviceRep libName := strings.Replace(library, "\\", "\\\\", -1) fixedContent := strings.Replace(string(sourceContent[:]), "", libName, 1) - rs.FinalizeJSON(ctx, tempdir+"/"+json.Basename(), fixedContent) + rs.finalizeJSON(ctx, tempdir+"/"+json.Basename(), fixedContent) env.AddPathStart("VK_LAYER_PATH", tempdir) return nil From 29181fcb512fafce85c8c14a267cf42c7c3dcdb8 Mon Sep 17 00:00:00 2001 From: Andrew Woloszyn Date: Wed, 23 May 2018 12:53:14 -0400 Subject: [PATCH 09/10] More comment updates. --- core/os/device/remotessh/commands.go | 2 +- core/os/device/remotessh/configuration.go | 55 ++++++++----------- .../os/device/remotessh/configuration_test.go | 6 +- core/os/device/remotessh/device.go | 2 +- 4 files changed, 30 insertions(+), 35 deletions(-) diff --git a/core/os/device/remotessh/commands.go b/core/os/device/remotessh/commands.go index 7932eeb708..fa90ba3475 100644 --- a/core/os/device/remotessh/commands.go +++ b/core/os/device/remotessh/commands.go @@ -226,7 +226,7 @@ func (b binding) ForwardPort(ctx context.Context, remotePort int) (int, error) { return } if err = b.doTunnel(ctx, local, remotePort); err != nil { - break + return } } }) diff --git a/core/os/device/remotessh/configuration.go b/core/os/device/remotessh/configuration.go index 56001020d1..8e76ef385b 100644 --- a/core/os/device/remotessh/configuration.go +++ b/core/os/device/remotessh/configuration.go @@ -17,7 +17,6 @@ package remotessh import ( "encoding/json" "io" - "io/ioutil" "os/user" ) @@ -42,41 +41,35 @@ type Configuration struct { KnownHosts string } -// UnmarshalJSON is used by json.Unmashall, this allows us to set -// up a default configuration, so that unknown parameters have -// sane defaults. -func (c *Configuration) UnmarshalJSON(data []byte) error { - type configAlias Configuration +// ReadConfigurations reads a set of configurations from then +// given reader, and returns the configurations to the user. +func ReadConfigurations(r io.Reader) ([]Configuration, error) { u, err := user.Current() if err != nil { - return err + return nil, err } - newC := &configAlias{ - Name: "", - Host: "", - User: u.Username, - Port: 22, - Keyfile: u.HomeDir + "/.ssh/id_rsa", - KnownHosts: u.HomeDir + "/.ssh/known_hosts", + + cfgs := []Configuration{} + d := json.NewDecoder(r) + if _, err := d.Token(); err != nil { + return nil, err } - if err := json.Unmarshal(data, newC); err != nil { - return err + for d.More() { + cfg := Configuration{ + Name: "", + Host: "", + User: u.Username, + Port: 22, + Keyfile: u.HomeDir + "/.ssh/id_rsa", + KnownHosts: u.HomeDir + "/.ssh/known_hosts", + } + if err := d.Decode(&cfg); err != nil { + return nil, err + } + cfgs = append(cfgs, cfg) } - - *c = Configuration(*newC) - return nil -} - -// ReadConfiguration reads a set of configurations from then -// given reader, and returns the configurations to the user. -func ReadConfiguration(r io.Reader) ([]Configuration, error) { - cfg := []Configuration{} - - bytes, err := ioutil.ReadAll(r) - if err != nil { + if _, err := d.Token(); err != nil { return nil, err } - - err = json.Unmarshal(bytes, &cfg) - return cfg, err + return cfgs, nil } diff --git a/core/os/device/remotessh/configuration_test.go b/core/os/device/remotessh/configuration_test.go index 59cb98fbb2..bb03526fd5 100644 --- a/core/os/device/remotessh/configuration_test.go +++ b/core/os/device/remotessh/configuration_test.go @@ -41,7 +41,9 @@ func TestReadConfiguration(t *testing.T) { "Name": "FirstConnection", "User": "me", "Host": "example.com", - "Port": 443 + "Port": 443, + "Keyfile": "~/.ssh/id_rsa", + "KnownHosts": "~/.ssh/known_hosts" }, { "Name": "Connection2", @@ -53,7 +55,7 @@ func TestReadConfiguration(t *testing.T) { ] ` reader := bytes.NewReader([]byte(input)) - configs, err := remotessh.ReadConfiguration(reader) + configs, err := remotessh.ReadConfigurations(reader) assert.With(ctx).That(err).Equals(nil) diff --git a/core/os/device/remotessh/device.go b/core/os/device/remotessh/device.go index 1e5be58f74..25258450e4 100644 --- a/core/os/device/remotessh/device.go +++ b/core/os/device/remotessh/device.go @@ -65,7 +65,7 @@ var _ Device = &binding{} // Devices returns the list of reachable SSH devices. func Devices(ctx context.Context, configuration io.Reader) ([]bind.Device, error) { - configurations, err := ReadConfiguration(configuration) + configurations, err := ReadConfigurations(configuration) if err != nil { return nil, err } From 3d2362dfe6fded7313045ba0c36f4ed80ddadacc Mon Sep 17 00:00:00 2001 From: Andrew Woloszyn Date: Wed, 23 May 2018 14:39:29 -0400 Subject: [PATCH 10/10] Fix to windows header includes --- cmd/device-info/main.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd/device-info/main.cpp b/cmd/device-info/main.cpp index 107ca16c8f..5aec04de07 100644 --- a/cmd/device-info/main.cpp +++ b/cmd/device-info/main.cpp @@ -19,6 +19,11 @@ #include #include +#if _WIN32 +#include +#include +#endif + #include "core/os/device/deviceinfo/cc/instance.h" #include "core/os/device/device.pb.h" #include