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

Commit 620469a4 authored by Elliott Hughes's avatar Elliott Hughes Committed by Gerrit Code Review
Browse files

Merge "Move "adb shell" over to getopt(3), and allow -tt on old devices."

parents 74552a9e cabfa117
Loading
Loading
Loading
Loading
+75 −75
Original line number Diff line number Diff line
@@ -686,100 +686,100 @@ static int RemoteShell(bool use_shell_protocol, const std::string& type_arg,
static int adb_shell(int argc, const char** argv) {
    FeatureSet features;
    std::string error;

    if (!adb_get_feature_set(&features, &error)) {
        fprintf(stderr, "error: %s\n", error.c_str());
        return 1;
    }

    bool use_shell_protocol = CanUseFeature(features, kFeatureShell2);
    if (!use_shell_protocol) {
        D("shell protocol not supported, using raw data transfer");
    } else {
        D("using shell protocol");
    }
    enum PtyAllocationMode { kPtyAuto, kPtyNo, kPtyYes, kPtyDefinitely };

    // Defaults.
    char escape_char = '~'; // -e
    bool use_shell_protocol = CanUseFeature(features, kFeatureShell2); // -x
    PtyAllocationMode tty = use_shell_protocol ? kPtyAuto : kPtyDefinitely; // -t/-T

    // Parse shell-specific command-line options.
    // argv[0] is always "shell".
    --argc;
    ++argv;
    int t_arg_count = 0;
    char escape_char = '~';
    while (argc) {
        if (!strcmp(argv[0], "-e")) {
            if (argc < 2 || !(strlen(argv[1]) == 1 || strcmp(argv[1], "none") == 0)) {
    argv[0] = "adb shell"; // So getopt(3) error messages start "adb shell".
    optind = 1; // argv[0] is always "shell", so set `optind` appropriately.
    int opt;
    while ((opt = getopt(argc, const_cast<char**>(argv), "+e:ntTx")) != -1) {
        switch (opt) {
            case 'e':
                if (!(strlen(optarg) == 1 || strcmp(optarg, "none") == 0)) {
                    fprintf(stderr, "error: -e requires a single-character argument or 'none'\n");
                    return 1;
                }
            escape_char = (strcmp(argv[1], "none") == 0) ? 0 : argv[1][0];
            argc -= 2;
            argv += 2;
        } else if (!strcmp(argv[0], "-T") || !strcmp(argv[0], "-t")) {
                escape_char = (strcmp(optarg, "none") == 0) ? 0 : optarg[0];
                break;
            case 'n':
                close_stdin();
                break;
            case 'x':
                // This option basically asks for historical behavior, so set options that
                // correspond to the historical defaults. This is slightly weird in that -Tx
                // is fine (because we'll undo the -T) but -xT isn't, but that does seem to
                // be our least worst choice...
                use_shell_protocol = false;
                tty = kPtyDefinitely;
                escape_char = '~';
                break;
            case 't':
                // Like ssh, -t arguments are cumulative so that multiple -t's
                // are needed to force a PTY.
            if (argv[0][1] == 't') {
                ++t_arg_count;
            } else {
                t_arg_count = -1;
            }
            --argc;
            ++argv;
        } else if (!strcmp(argv[0], "-x")) {
            use_shell_protocol = false;
            --argc;
            ++argv;
        } else if (!strcmp(argv[0], "-n")) {
            close_stdin();

            --argc;
            ++argv;
        } else {
                tty = (tty >= kPtyYes) ? kPtyDefinitely : kPtyYes;
                break;
            case 'T':
                tty = kPtyNo;
                break;
            default:
                // getopt(3) already printed an error message for us.
                return 1;
        }
    }

    // Legacy shell protocol requires a remote PTY to close the subprocess properly which creates
    // some weird interactions with -tT.
    if (!use_shell_protocol && t_arg_count != 0) {
        if (!CanUseFeature(features, kFeatureShell2)) {
            fprintf(stderr, "error: target doesn't support PTY args -Tt\n");
        } else {
            fprintf(stderr, "error: PTY args -Tt cannot be used with -x\n");
        }
        return 1;
    }
    bool is_interactive = (optind == argc);

    std::string shell_type_arg;
    if (CanUseFeature(features, kFeatureShell2)) {
        if (t_arg_count < 0) {
    std::string shell_type_arg = kShellServiceArgPty;
    if (tty == kPtyNo) {
        shell_type_arg = kShellServiceArgRaw;
        } else if (t_arg_count == 0) {
    } else if (tty == kPtyAuto) {
        // If stdin isn't a TTY, default to a raw shell; this lets
        // things like `adb shell < my_script.sh` work as expected.
            // Otherwise leave |shell_type_arg| blank which uses PTY for
            // interactive shells and raw for non-interactive.
            if (!unix_isatty(STDIN_FILENO)) {
        // Non-interactive shells should also not have a pty.
        if (!unix_isatty(STDIN_FILENO) || !is_interactive) {
            shell_type_arg = kShellServiceArgRaw;
        }
        } else if (t_arg_count == 1) {
    } else if (tty == kPtyYes) {
        // A single -t arg isn't enough to override implicit -T.
        if (!unix_isatty(STDIN_FILENO)) {
            fprintf(stderr,
                    "Remote PTY will not be allocated because stdin is not a terminal.\n"
                    "Use multiple -t options to force remote PTY allocation.\n");
            shell_type_arg = kShellServiceArgRaw;
            } else {
                shell_type_arg = kShellServiceArgPty;
        }
    }

    D("shell -e 0x%x t=%d use_shell_protocol=%s shell_type_arg=%s\n",
      escape_char, tty,
      use_shell_protocol ? "true" : "false",
      (shell_type_arg == kShellServiceArgPty) ? "pty" : "raw");

    // Raw mode is only supported when talking to a new device *and* using the shell protocol.
    if (!use_shell_protocol) {
        if (shell_type_arg != kShellServiceArgPty) {
            fprintf(stderr, "error: %s only supports allocating a pty\n",
                    !CanUseFeature(features, kFeatureShell2) ? "device" : "-x");
            return 1;
        } else {
            shell_type_arg = kShellServiceArgPty;
            // If we're not using the shell protocol, the type argument must be empty.
            shell_type_arg = "";
        }
    }

    std::string command;
    if (argc) {
    if (optind < argc) {
        // We don't escape here, just like ssh(1). http://b/20564385.
        command = android::base::Join(std::vector<const char*>(argv, argv + argc), ' ');
        command = android::base::Join(std::vector<const char*>(argv + optind, argv + argc), ' ');
    }

    return RemoteShell(use_shell_protocol, shell_type_arg, escape_char, command);
+24 −13
Original line number Diff line number Diff line
@@ -371,15 +371,8 @@ class ShellTest(DeviceTest):
    def test_pty_logic(self):
        """Tests that a PTY is allocated when it should be.

        PTY allocation behavior should match ssh; some behavior requires
        a terminal stdin to test so this test will be skipped if stdin
        is not a terminal.
        PTY allocation behavior should match ssh.
        """
        if not self.device.has_shell_protocol():
            raise unittest.SkipTest('PTY arguments unsupported on this device')
        if not os.isatty(sys.stdin.fileno()):
            raise unittest.SkipTest('PTY tests require stdin terminal')

        def check_pty(args):
            """Checks adb shell PTY allocation.

@@ -409,6 +402,8 @@ class ShellTest(DeviceTest):
        # -T: never allocate PTY.
        self.assertEqual((False, False), check_pty(['-T']))

        # These tests require a new device.
        if self.device.has_shell_protocol() and os.isatty(sys.stdin.fileno()):
            # No args: PTY only if stdin is a terminal and shell is interactive,
            # which is difficult to reliably test from a script.
            self.assertEqual((False, False), check_pty([]))
@@ -419,6 +414,22 @@ class ShellTest(DeviceTest):
        # -t -t: always allocate PTY.
        self.assertEqual((True, True), check_pty(['-t', '-t']))

        # -tt: always allocate PTY, POSIX style (http://b/32216152).
        self.assertEqual((True, True), check_pty(['-tt']))

        # -ttt: ssh has weird even/odd behavior with multiple -t flags, but
        # we follow the man page instead.
        self.assertEqual((True, True), check_pty(['-ttt']))

        # -ttx: -x and -tt aren't incompatible (though -Tx would be an error).
        self.assertEqual((True, True), check_pty(['-ttx']))

        # -Ttt: -tt cancels out -T.
        self.assertEqual((True, True), check_pty(['-Ttt']))

        # -ttT: -T cancels out -tt.
        self.assertEqual((False, False), check_pty(['-ttT']))

    def test_shell_protocol(self):
        """Tests the shell protocol on the device.