Home | History | Annotate | Download | only in adb
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #include "shell_service.h"
     18 
     19 #include <gtest/gtest.h>
     20 
     21 #include <signal.h>
     22 
     23 #include <string>
     24 #include <vector>
     25 
     26 #include <android-base/strings.h>
     27 
     28 #include "adb.h"
     29 #include "adb_io.h"
     30 #include "sysdeps.h"
     31 
     32 class ShellServiceTest : public ::testing::Test {
     33   public:
     34     static void SetUpTestCase() {
     35         // This is normally done in main.cpp.
     36         saved_sigpipe_handler_ = signal(SIGPIPE, SIG_IGN);
     37 
     38     }
     39 
     40     static void TearDownTestCase() {
     41         signal(SIGPIPE, saved_sigpipe_handler_);
     42     }
     43 
     44     // Helpers to start and cleanup a subprocess. Cleanup normally does not
     45     // need to be called manually unless multiple subprocesses are run from
     46     // a single test.
     47     void StartTestSubprocess(const char* command, SubprocessType type,
     48                              SubprocessProtocol protocol);
     49     void CleanupTestSubprocess();
     50 
     51     virtual void TearDown() override {
     52         void CleanupTestSubprocess();
     53     }
     54 
     55     static sighandler_t saved_sigpipe_handler_;
     56 
     57     int subprocess_fd_ = -1;
     58     int shell_exit_receiver_fd_ = -1, saved_shell_exit_fd_;
     59 };
     60 
     61 sighandler_t ShellServiceTest::saved_sigpipe_handler_ = nullptr;
     62 
     63 void ShellServiceTest::StartTestSubprocess(
     64         const char* command, SubprocessType type, SubprocessProtocol protocol) {
     65     // We want to intercept the shell exit message to make sure it's sent.
     66     saved_shell_exit_fd_ = SHELL_EXIT_NOTIFY_FD;
     67     int fd[2];
     68     ASSERT_TRUE(adb_socketpair(fd) >= 0);
     69     SHELL_EXIT_NOTIFY_FD = fd[0];
     70     shell_exit_receiver_fd_ = fd[1];
     71 
     72     subprocess_fd_ = StartSubprocess(command, nullptr, type, protocol);
     73     ASSERT_TRUE(subprocess_fd_ >= 0);
     74 }
     75 
     76 void ShellServiceTest::CleanupTestSubprocess() {
     77     if (subprocess_fd_ >= 0) {
     78         // Subprocess should send its FD to SHELL_EXIT_NOTIFY_FD for cleanup.
     79         int notified_fd = -1;
     80         ASSERT_TRUE(ReadFdExactly(shell_exit_receiver_fd_, &notified_fd,
     81                                   sizeof(notified_fd)));
     82         ASSERT_EQ(notified_fd, subprocess_fd_);
     83 
     84         adb_close(subprocess_fd_);
     85         subprocess_fd_ = -1;
     86 
     87         // Restore SHELL_EXIT_NOTIFY_FD.
     88         adb_close(SHELL_EXIT_NOTIFY_FD);
     89         adb_close(shell_exit_receiver_fd_);
     90         shell_exit_receiver_fd_ = -1;
     91         SHELL_EXIT_NOTIFY_FD = saved_shell_exit_fd_;
     92     }
     93 }
     94 
     95 namespace {
     96 
     97 // Reads raw data from |fd| until it closes or errors.
     98 std::string ReadRaw(int fd) {
     99     char buffer[1024];
    100     char *cur_ptr = buffer, *end_ptr = buffer + sizeof(buffer);
    101 
    102     while (1) {
    103         int bytes = adb_read(fd, cur_ptr, end_ptr - cur_ptr);
    104         if (bytes <= 0) {
    105             return std::string(buffer, cur_ptr);
    106         }
    107         cur_ptr += bytes;
    108     }
    109 }
    110 
    111 // Reads shell protocol data from |fd| until it closes or errors. Fills
    112 // |stdout| and |stderr| with their respective data, and returns the exit code
    113 // read from the protocol or -1 if an exit code packet was not received.
    114 int ReadShellProtocol(int fd, std::string* stdout, std::string* stderr) {
    115     int exit_code = -1;
    116     stdout->clear();
    117     stderr->clear();
    118 
    119     ShellProtocol* protocol = new ShellProtocol(fd);
    120     while (protocol->Read()) {
    121         switch (protocol->id()) {
    122             case ShellProtocol::kIdStdout:
    123                 stdout->append(protocol->data(), protocol->data_length());
    124                 break;
    125             case ShellProtocol::kIdStderr:
    126                 stderr->append(protocol->data(), protocol->data_length());
    127                 break;
    128             case ShellProtocol::kIdExit:
    129                 EXPECT_EQ(-1, exit_code) << "Multiple exit packets received";
    130                 EXPECT_EQ(1u, protocol->data_length());
    131                 exit_code = protocol->data()[0];
    132                 break;
    133             default:
    134                 ADD_FAILURE() << "Unidentified packet ID: " << protocol->id();
    135         }
    136     }
    137     delete protocol;
    138 
    139     return exit_code;
    140 }
    141 
    142 // Checks if each line in |lines| exists in the same order in |output|. Blank
    143 // lines in |output| are ignored for simplicity.
    144 bool ExpectLinesEqual(const std::string& output,
    145                       const std::vector<std::string>& lines) {
    146     auto output_lines = android::base::Split(output, "\r\n");
    147     size_t i = 0;
    148 
    149     for (const std::string& line : lines) {
    150         // Skip empty lines in output.
    151         while (i < output_lines.size() && output_lines[i].empty()) {
    152             ++i;
    153         }
    154         if (i >= output_lines.size()) {
    155             ADD_FAILURE() << "Ran out of output lines";
    156             return false;
    157         }
    158         EXPECT_EQ(line, output_lines[i]);
    159         ++i;
    160     }
    161 
    162     while (i < output_lines.size() && output_lines[i].empty()) {
    163         ++i;
    164     }
    165     EXPECT_EQ(i, output_lines.size()) << "Found unmatched output lines";
    166     return true;
    167 }
    168 
    169 }  // namespace
    170 
    171 // Tests a raw subprocess with no protocol.
    172 TEST_F(ShellServiceTest, RawNoProtocolSubprocess) {
    173     // [ -t 0 ] checks if stdin is connected to a terminal.
    174     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
    175             "echo foo; echo bar >&2; [ -t 0 ]; echo $?",
    176             SubprocessType::kRaw, SubprocessProtocol::kNone));
    177 
    178     // [ -t 0 ] == 0 means we have a terminal (PTY). Even when requesting a raw subprocess, without
    179     // the shell protocol we should always force a PTY to ensure proper cleanup.
    180     ExpectLinesEqual(ReadRaw(subprocess_fd_), {"foo", "bar", "0"});
    181 }
    182 
    183 // Tests a PTY subprocess with no protocol.
    184 TEST_F(ShellServiceTest, PtyNoProtocolSubprocess) {
    185     // [ -t 0 ] checks if stdin is connected to a terminal.
    186     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
    187             "echo foo; echo bar >&2; [ -t 0 ]; echo $?",
    188             SubprocessType::kPty, SubprocessProtocol::kNone));
    189 
    190     // [ -t 0 ] == 0 means we have a terminal (PTY).
    191     ExpectLinesEqual(ReadRaw(subprocess_fd_), {"foo", "bar", "0"});
    192 }
    193 
    194 // Tests a raw subprocess with the shell protocol.
    195 TEST_F(ShellServiceTest, RawShellProtocolSubprocess) {
    196     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
    197             "echo foo; echo bar >&2; echo baz; exit 24",
    198             SubprocessType::kRaw, SubprocessProtocol::kShell));
    199 
    200     std::string stdout, stderr;
    201     EXPECT_EQ(24, ReadShellProtocol(subprocess_fd_, &stdout, &stderr));
    202     ExpectLinesEqual(stdout, {"foo", "baz"});
    203     ExpectLinesEqual(stderr, {"bar"});
    204 }
    205 
    206 // Tests a PTY subprocess with the shell protocol.
    207 TEST_F(ShellServiceTest, PtyShellProtocolSubprocess) {
    208     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
    209             "echo foo; echo bar >&2; echo baz; exit 50",
    210             SubprocessType::kPty, SubprocessProtocol::kShell));
    211 
    212     // PTY always combines stdout and stderr but the shell protocol should
    213     // still give us an exit code.
    214     std::string stdout, stderr;
    215     EXPECT_EQ(50, ReadShellProtocol(subprocess_fd_, &stdout, &stderr));
    216     ExpectLinesEqual(stdout, {"foo", "bar", "baz"});
    217     ExpectLinesEqual(stderr, {});
    218 }
    219 
    220 // Tests an interactive PTY session.
    221 TEST_F(ShellServiceTest, InteractivePtySubprocess) {
    222     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
    223             "", SubprocessType::kPty, SubprocessProtocol::kShell));
    224 
    225     // Use variable substitution so echoed input is different from output.
    226     const char* commands[] = {"TEST_STR=abc123",
    227                               "echo --${TEST_STR}--",
    228                               "exit"};
    229 
    230     ShellProtocol* protocol = new ShellProtocol(subprocess_fd_);
    231     for (std::string command : commands) {
    232         // Interactive shell requires a newline to complete each command.
    233         command.push_back('\n');
    234         memcpy(protocol->data(), command.data(), command.length());
    235         ASSERT_TRUE(protocol->Write(ShellProtocol::kIdStdin, command.length()));
    236     }
    237     delete protocol;
    238 
    239     std::string stdout, stderr;
    240     EXPECT_EQ(0, ReadShellProtocol(subprocess_fd_, &stdout, &stderr));
    241     // An unpredictable command prompt makes parsing exact output difficult but
    242     // it should at least contain echoed input and the expected output.
    243     for (const char* command : commands) {
    244         EXPECT_FALSE(stdout.find(command) == std::string::npos);
    245     }
    246     EXPECT_FALSE(stdout.find("--abc123--") == std::string::npos);
    247 }
    248 
    249 // Tests closing raw subprocess stdin.
    250 TEST_F(ShellServiceTest, CloseClientStdin) {
    251     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
    252             "cat; echo TEST_DONE",
    253             SubprocessType::kRaw, SubprocessProtocol::kShell));
    254 
    255     std::string input = "foo\nbar";
    256     ShellProtocol* protocol = new ShellProtocol(subprocess_fd_);
    257     memcpy(protocol->data(), input.data(), input.length());
    258     ASSERT_TRUE(protocol->Write(ShellProtocol::kIdStdin, input.length()));
    259     ASSERT_TRUE(protocol->Write(ShellProtocol::kIdCloseStdin, 0));
    260     delete protocol;
    261 
    262     std::string stdout, stderr;
    263     EXPECT_EQ(0, ReadShellProtocol(subprocess_fd_, &stdout, &stderr));
    264     ExpectLinesEqual(stdout, {"foo", "barTEST_DONE"});
    265     ExpectLinesEqual(stderr, {});
    266 }
    267 
    268 // Tests that nothing breaks when the stdin/stdout pipe closes.
    269 TEST_F(ShellServiceTest, CloseStdinStdoutSubprocess) {
    270     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
    271             "exec 0<&-; exec 1>&-; echo bar >&2",
    272             SubprocessType::kRaw, SubprocessProtocol::kShell));
    273 
    274     std::string stdout, stderr;
    275     EXPECT_EQ(0, ReadShellProtocol(subprocess_fd_, &stdout, &stderr));
    276     ExpectLinesEqual(stdout, {});
    277     ExpectLinesEqual(stderr, {"bar"});
    278 }
    279 
    280 // Tests that nothing breaks when the stderr pipe closes.
    281 TEST_F(ShellServiceTest, CloseStderrSubprocess) {
    282     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
    283             "exec 2>&-; echo foo",
    284             SubprocessType::kRaw, SubprocessProtocol::kShell));
    285 
    286     std::string stdout, stderr;
    287     EXPECT_EQ(0, ReadShellProtocol(subprocess_fd_, &stdout, &stderr));
    288     ExpectLinesEqual(stdout, {"foo"});
    289     ExpectLinesEqual(stderr, {});
    290 }
    291