| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * 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. |
| */ |
| |
| /* Utility that uses an adb connection as the login shell. */ |
| |
| #include <array> |
| #include <cassert> |
| #include <cstdio> |
| #include <cstring> |
| #include <string> |
| #include <vector> |
| #include <unistd.h> |
| |
| // Many of our users interact with CVDs via ssh. They expect to be able to |
| // get an Android shell (as opposed to the host shell) with a single command. |
| // |
| // Our goals are to: |
| // |
| // * Allow the user to select which CVD to connect to |
| // |
| // * Avoid modifications to the host-side sshd and the protocol |
| // |
| // We accomplish this by using specialized accounts: vsoc-## and cvd-## and |
| // specific Android serial numbers: |
| // |
| // The vsoc-01 account provides a host-side shell that controls the first CVD |
| // The cvd-01 account is connected to the Andorid shell of the first CVD |
| // The first CVD has a serial number of CUTTLEFISHCVD01 |
| // |
| // The code in the commands/launch directory also follows these conventions by |
| // default. |
| // |
| |
| namespace { |
| std::string InstanceNumberAsStr() { |
| static const char kUserPrefix[] = "cvd-"; |
| |
| std::string user{std::getenv("USER")}; |
| return user.rfind(kUserPrefix, 0) == 0 // starts_with |
| ? user.substr(sizeof(kUserPrefix) - 1) |
| : "01"; |
| } |
| |
| int InstanceNumberAsInt() { |
| auto instance_str = InstanceNumberAsStr(); |
| char* end{}; |
| instance_str.push_back('\0'); |
| auto result = static_cast<int>(std::strtol(&instance_str[0], &end, 10)); |
| return *end || result < 1 ? 1 : result; |
| } |
| |
| std::string TCPInstanceStr() { |
| static constexpr int kFirstPort = 6520; |
| const char kIPPrefix[] = "127.0.0.1:"; |
| |
| auto instance_port = InstanceNumberAsInt() - 1 + kFirstPort; |
| return std::string{kIPPrefix} + std::to_string(instance_port); |
| } |
| |
| std::string USBInstanceStr() { |
| const char kSerialNumberPrefix[] = "CUTTLEFISHCVD"; |
| std::string instance = InstanceNumberAsStr(); |
| return std::string{kSerialNumberPrefix} + InstanceNumberAsStr(); |
| } |
| |
| std::string InstanceStr() { |
| std::string possible_device_names[] = {TCPInstanceStr(), USBInstanceStr()}; |
| |
| FILE* adb_devices_cmd_stream = popen("/usr/bin/adb devices", "r"); |
| std::array<char, 128> line{}; |
| while (fgets(line.data(), line.size(), adb_devices_cmd_stream) != nullptr) { |
| for (const auto& device_name : possible_device_names) { |
| if (std::string{line.data()}.find(device_name) != std::string::npos) { |
| return device_name; |
| } |
| } |
| } |
| return nullptr; |
| } |
| |
| std::string VsocUserName() { |
| const char kVsocUserPrefix[] = "vsoc-"; |
| auto num = InstanceNumberAsStr(); |
| return std::string{kVsocUserPrefix} + num; |
| } |
| |
| std::string VsocHomeAdbShellPath() { |
| return std::string{"/home/"} + VsocUserName() + "/bin/adbshell"; |
| } |
| |
| void TryExecHomeAdbShell(char* argv[]) { |
| auto home_shell = VsocHomeAdbShellPath(); |
| if (access(home_shell.c_str(), X_OK) != -1 && home_shell != argv[0]) { |
| home_shell.push_back('\0'); |
| argv[0] = &home_shell[0]; |
| execv(argv[0], argv); |
| assert(0 && "execv() returned"); |
| } |
| } |
| } // namespace |
| |
| int main(int argc, char* argv[]) { |
| TryExecHomeAdbShell(argv); |
| auto instance = InstanceStr(); |
| std::vector<char*> new_argv = { |
| const_cast<char*>("/usr/bin/adb"), const_cast<char*>("-s"), |
| const_cast<char*>(instance.c_str()), const_cast<char*>("shell"), |
| const_cast<char*>("/system/bin/sh")}; |
| |
| // Some important data is lost before this point, and there are |
| // no great recovery options: |
| // * ssh with no arguments comes in with 1 arg of -adbshell. The command |
| // given above does the right thing if we don't invoke the shell. |
| if (argc == 1) { |
| new_argv.back() = nullptr; |
| } |
| // * simple shell commands come in with a -c and a single string. The |
| // problem here is that adb doesn't preserve spaces, so we need |
| // to do additional escaping. The best compromise seems to be to |
| // throw double quotes around each string. |
| for (int i = 1; i < argc; ++i) { |
| size_t buf_size = std::strlen(argv[i]) + 4; |
| new_argv.push_back(new char[buf_size]); |
| std::snprintf(new_argv.back(), buf_size, "\"%s\"", argv[i]); |
| } |
| // |
| // * scp seems to be pathologically broken when paths contain spaces. |
| // spaces aren't properly escaped by gcloud, so scp will fail with |
| // "scp: with ambiguous target." We might be able to fix this with |
| // some creative parsing of the arguments, but that seems like |
| // overkill. |
| new_argv.push_back(nullptr); |
| execv(new_argv[0], new_argv.data()); |
| // This never should happen |
| return 2; |
| } |