• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
SetUpTestCase()34     static void SetUpTestCase() {
35         // This is normally done in main.cpp.
36         saved_sigpipe_handler_ = signal(SIGPIPE, SIG_IGN);
37 
38     }
39 
TearDownTestCase()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 
TearDown()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 
StartTestSubprocess(const char * command,SubprocessType type,SubprocessProtocol protocol)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 
CleanupTestSubprocess()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.
ReadRaw(int fd)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.
ReadShellProtocol(int fd,std::string * stdout,std::string * stderr)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.
ExpectLinesEqual(const std::string & output,const std::vector<std::string> & lines)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.
TEST_F(ShellServiceTest,RawNoProtocolSubprocess)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.
TEST_F(ShellServiceTest,PtyNoProtocolSubprocess)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.
TEST_F(ShellServiceTest,RawShellProtocolSubprocess)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.
TEST_F(ShellServiceTest,PtyShellProtocolSubprocess)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.
TEST_F(ShellServiceTest,InteractivePtySubprocess)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.
TEST_F(ShellServiceTest,CloseClientStdin)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.
TEST_F(ShellServiceTest,CloseStdinStdoutSubprocess)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.
TEST_F(ShellServiceTest,CloseStderrSubprocess)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