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 "shell_protocol.h"
31 #include "sysdeps.h"
32 
33 class ShellServiceTest : public ::testing::Test {
34   public:
35     static void SetUpTestCase() {
36         // This is normally done in main.cpp.
37         saved_sigpipe_handler_ = signal(SIGPIPE, SIG_IGN);
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     void StartTestCommandInProcess(std::string name, Command command, SubprocessProtocol protocol);
52 
53     virtual void TearDown() override { CleanupTestSubprocess(); }
54 
55     static sighandler_t saved_sigpipe_handler_;
56 
57     unique_fd command_fd_;
58 };
59 
60 sighandler_t ShellServiceTest::saved_sigpipe_handler_ = nullptr;
61 
62 void ShellServiceTest::StartTestSubprocess(
63         const char* command, SubprocessType type, SubprocessProtocol protocol) {
64     command_fd_ = StartSubprocess(command, nullptr, type, protocol);
65     ASSERT_TRUE(command_fd_ >= 0);
66 }
67 
68 void ShellServiceTest::CleanupTestSubprocess() {
69 }
70 
71 void ShellServiceTest::StartTestCommandInProcess(std::string name, Command command,
72                                                  SubprocessProtocol protocol) {
73     command_fd_ = StartCommandInProcess(std::move(name), std::move(command), protocol);
74     ASSERT_TRUE(command_fd_ >= 0);
75 }
76 
77 namespace {
78 
79 // Reads raw data from |fd| until it closes or errors.
80 std::string ReadRaw(borrowed_fd fd) {
81     char buffer[1024];
82     char *cur_ptr = buffer, *end_ptr = buffer + sizeof(buffer);
83 
84     while (1) {
85         int bytes = adb_read(fd, cur_ptr, end_ptr - cur_ptr);
86         if (bytes <= 0) {
87             return std::string(buffer, cur_ptr);
88         }
89         cur_ptr += bytes;
90     }
91 }
92 
93 // Reads shell protocol data from |fd| until it closes or errors. Fills
94 // |stdout| and |stderr| with their respective data, and returns the exit code
95 // read from the protocol or -1 if an exit code packet was not received.
96 int ReadShellProtocol(borrowed_fd fd, std::string* stdout, std::string* stderr) {
97     int exit_code = -1;
98     stdout->clear();
99     stderr->clear();
100 
101     auto protocol = std::make_unique<ShellProtocol>(fd.get());
102     while (protocol->Read()) {
103         switch (protocol->id()) {
104             case ShellProtocol::kIdStdout:
105                 stdout->append(protocol->data(), protocol->data_length());
106                 break;
107             case ShellProtocol::kIdStderr:
108                 stderr->append(protocol->data(), protocol->data_length());
109                 break;
110             case ShellProtocol::kIdExit:
111                 EXPECT_EQ(-1, exit_code) << "Multiple exit packets received";
112                 EXPECT_EQ(1u, protocol->data_length());
113                 exit_code = protocol->data()[0];
114                 break;
115             default:
116                 ADD_FAILURE() << "Unidentified packet ID: " << protocol->id();
117         }
118     }
119 
120     return exit_code;
121 }
122 
123 // Checks if each line in |lines| exists in the same order in |output|. Blank
124 // lines in |output| are ignored for simplicity.
125 bool ExpectLinesEqual(const std::string& output,
126                       const std::vector<std::string>& lines) {
127     auto output_lines = android::base::Split(output, "\r\n");
128     size_t i = 0;
129 
130     for (const std::string& line : lines) {
131         // Skip empty lines in output.
132         while (i < output_lines.size() && output_lines[i].empty()) {
133             ++i;
134         }
135         if (i >= output_lines.size()) {
136             ADD_FAILURE() << "Ran out of output lines";
137             return false;
138         }
139         EXPECT_EQ(line, output_lines[i]);
140         ++i;
141     }
142 
143     while (i < output_lines.size() && output_lines[i].empty()) {
144         ++i;
145     }
146     EXPECT_EQ(i, output_lines.size()) << "Found unmatched output lines";
147     return true;
148 }
149 
150 }  // namespace
151 
152 // Tests a raw subprocess with no protocol.
153 TEST_F(ShellServiceTest, RawNoProtocolSubprocess) {
154     // [ -t 0 ] checks if stdin is connected to a terminal.
155     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
156             "echo foo; echo bar >&2; [ -t 0 ]; echo $?",
157             SubprocessType::kRaw, SubprocessProtocol::kNone));
158 
159     // [ -t 0 ] == 0 means we have a terminal (PTY). Even when requesting a raw subprocess, without
160     // the shell protocol we should always force a PTY to ensure proper cleanup.
161     ExpectLinesEqual(ReadRaw(command_fd_), {"foo", "bar", "0"});
162 }
163 
164 // Tests a PTY subprocess with no protocol.
165 TEST_F(ShellServiceTest, PtyNoProtocolSubprocess) {
166     // [ -t 0 ] checks if stdin is connected to a terminal.
167     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
168             "echo foo; echo bar >&2; [ -t 0 ]; echo $?",
169             SubprocessType::kPty, SubprocessProtocol::kNone));
170 
171     // [ -t 0 ] == 0 means we have a terminal (PTY).
172     ExpectLinesEqual(ReadRaw(command_fd_), {"foo", "bar", "0"});
173 }
174 
175 // Tests a raw subprocess with the shell protocol.
176 TEST_F(ShellServiceTest, RawShellProtocolSubprocess) {
177     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
178             "echo foo; echo bar >&2; echo baz; exit 24",
179             SubprocessType::kRaw, SubprocessProtocol::kShell));
180 
181     std::string stdout, stderr;
182     EXPECT_EQ(24, ReadShellProtocol(command_fd_, &stdout, &stderr));
183     ExpectLinesEqual(stdout, {"foo", "baz"});
184     ExpectLinesEqual(stderr, {"bar"});
185 }
186 
187 // Tests a PTY subprocess with the shell protocol.
188 TEST_F(ShellServiceTest, PtyShellProtocolSubprocess) {
189     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
190             "echo foo; echo bar >&2; echo baz; exit 50",
191             SubprocessType::kPty, SubprocessProtocol::kShell));
192 
193     // PTY always combines stdout and stderr but the shell protocol should
194     // still give us an exit code.
195     std::string stdout, stderr;
196     EXPECT_EQ(50, ReadShellProtocol(command_fd_, &stdout, &stderr));
197     ExpectLinesEqual(stdout, {"foo", "bar", "baz"});
198     ExpectLinesEqual(stderr, {});
199 }
200 
201 // Tests an interactive PTY session.
202 TEST_F(ShellServiceTest, InteractivePtySubprocess) {
203     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
204             "", SubprocessType::kPty, SubprocessProtocol::kShell));
205 
206     // Use variable substitution so echoed input is different from output.
207     const char* commands[] = {"TEST_STR=abc123",
208                               "echo --${TEST_STR}--",
209                               "exit"};
210 
211     ShellProtocol* protocol = new ShellProtocol(command_fd_);
212     for (std::string command : commands) {
213         // Interactive shell requires a newline to complete each command.
214         command.push_back('\n');
215         memcpy(protocol->data(), command.data(), command.length());
216         ASSERT_TRUE(protocol->Write(ShellProtocol::kIdStdin, command.length()));
217     }
218     delete protocol;
219 
220     std::string stdout, stderr;
221     EXPECT_EQ(0, ReadShellProtocol(command_fd_, &stdout, &stderr));
222     // An unpredictable command prompt makes parsing exact output difficult but
223     // it should at least contain echoed input and the expected output.
224     for (const char* command : commands) {
225         EXPECT_FALSE(stdout.find(command) == std::string::npos);
226     }
227     EXPECT_FALSE(stdout.find("--abc123--") == std::string::npos);
228 }
229 
230 // Tests closing raw subprocess stdin.
231 TEST_F(ShellServiceTest, CloseClientStdin) {
232     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
233             "cat; echo TEST_DONE",
234             SubprocessType::kRaw, SubprocessProtocol::kShell));
235 
236     std::string input = "foo\nbar";
237     ShellProtocol* protocol = new ShellProtocol(command_fd_);
238     memcpy(protocol->data(), input.data(), input.length());
239     ASSERT_TRUE(protocol->Write(ShellProtocol::kIdStdin, input.length()));
240     ASSERT_TRUE(protocol->Write(ShellProtocol::kIdCloseStdin, 0));
241     delete protocol;
242 
243     std::string stdout, stderr;
244     EXPECT_EQ(0, ReadShellProtocol(command_fd_, &stdout, &stderr));
245     ExpectLinesEqual(stdout, {"foo", "barTEST_DONE"});
246     ExpectLinesEqual(stderr, {});
247 }
248 
249 // Tests that nothing breaks when the stdin/stdout pipe closes.
250 TEST_F(ShellServiceTest, CloseStdinStdoutSubprocess) {
251     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
252             "exec 0<&-; exec 1>&-; echo bar >&2",
253             SubprocessType::kRaw, SubprocessProtocol::kShell));
254 
255     std::string stdout, stderr;
256     EXPECT_EQ(0, ReadShellProtocol(command_fd_, &stdout, &stderr));
257     ExpectLinesEqual(stdout, {});
258     ExpectLinesEqual(stderr, {"bar"});
259 }
260 
261 // Tests that nothing breaks when the stderr pipe closes.
262 TEST_F(ShellServiceTest, CloseStderrSubprocess) {
263     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
264             "exec 2>&-; echo foo",
265             SubprocessType::kRaw, SubprocessProtocol::kShell));
266 
267     std::string stdout, stderr;
268     EXPECT_EQ(0, ReadShellProtocol(command_fd_, &stdout, &stderr));
269     ExpectLinesEqual(stdout, {"foo"});
270     ExpectLinesEqual(stderr, {});
271 }
272 
273 // Tests an inprocess command with no protocol.
274 TEST_F(ShellServiceTest, RawNoProtocolInprocess) {
275     ASSERT_NO_FATAL_FAILURE(
276             StartTestCommandInProcess("123",
277                                       [](auto args, auto in, auto out, auto err) -> int {
278                                           EXPECT_EQ("123", args);
279                                           char input[10];
280                                           EXPECT_TRUE(ReadFdExactly(in, input, 2));
281                                           input[2] = 0;
282                                           EXPECT_STREQ("in", input);
283                                           WriteFdExactly(out, "out\n");
284                                           WriteFdExactly(err, "err\n");
285                                           return 0;
286                                       },
287                                       SubprocessProtocol::kNone));
288 
289     WriteFdExactly(command_fd_, "in");
290     ExpectLinesEqual(ReadRaw(command_fd_), {"out", "err"});
291 }
292 
293 // Tests an inprocess command with the shell protocol.
294 TEST_F(ShellServiceTest, RawShellProtocolInprocess) {
295     ASSERT_NO_FATAL_FAILURE(
296             StartTestCommandInProcess("321",
297                                       [](auto args, auto in, auto out, auto err) -> int {
298                                           EXPECT_EQ("321", args);
299                                           char input[10];
300                                           EXPECT_TRUE(ReadFdExactly(in, input, 2));
301                                           input[2] = 0;
302                                           EXPECT_STREQ("in", input);
303                                           WriteFdExactly(out, "out\n");
304                                           WriteFdExactly(err, "err\n");
305                                           return 0;
306                                       },
307                                       SubprocessProtocol::kShell));
308 
309     {
310         auto write_protocol = std::make_unique<ShellProtocol>(command_fd_);
311         memcpy(write_protocol->data(), "in", 2);
312         write_protocol->Write(ShellProtocol::kIdStdin, 2);
313     }
314 
315     std::string stdout, stderr;
316     // For in-process commands the exit code is always the default (1).
317     EXPECT_EQ(1, ReadShellProtocol(command_fd_, &stdout, &stderr));
318     ExpectLinesEqual(stdout, {"out"});
319     ExpectLinesEqual(stderr, {"err"});
320 }
321