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 #include "test_utils/test_utils.h"
33 
34 using namespace test_utils;
35 
36 class ShellServiceTest : public ::testing::Test {
37   public:
SetUpTestCase()38     static void SetUpTestCase() {
39         // This is normally done in main.cpp.
40         saved_sigpipe_handler_ = signal(SIGPIPE, SIG_IGN);
41     }
42 
TearDownTestCase()43     static void TearDownTestCase() {
44         signal(SIGPIPE, saved_sigpipe_handler_);
45     }
46 
47     // Helpers to start and cleanup a subprocess. Cleanup normally does not
48     // need to be called manually unless multiple subprocesses are run from
49     // a single test.
50     void StartTestSubprocess(const char* command, SubprocessType type,
51                              SubprocessProtocol protocol);
52     void CleanupTestSubprocess();
53 
54     void StartTestCommandInProcess(std::string name, Command command, SubprocessProtocol protocol);
55 
TearDown()56     virtual void TearDown() override { CleanupTestSubprocess(); }
57 
58     static sighandler_t saved_sigpipe_handler_;
59 
60     unique_fd command_fd_;
61 };
62 
63 sighandler_t ShellServiceTest::saved_sigpipe_handler_ = nullptr;
64 
StartTestSubprocess(const char * command,SubprocessType type,SubprocessProtocol protocol)65 void ShellServiceTest::StartTestSubprocess(
66         const char* command, SubprocessType type, SubprocessProtocol protocol) {
67     command_fd_ = StartSubprocess(command, nullptr, type, protocol);
68     ASSERT_TRUE(command_fd_ >= 0);
69 }
70 
CleanupTestSubprocess()71 void ShellServiceTest::CleanupTestSubprocess() {
72 }
73 
StartTestCommandInProcess(std::string name,Command command,SubprocessProtocol protocol)74 void ShellServiceTest::StartTestCommandInProcess(std::string name, Command command,
75                                                  SubprocessProtocol protocol) {
76     command_fd_ = StartCommandInProcess(std::move(name), std::move(command), protocol);
77     ASSERT_TRUE(command_fd_ >= 0);
78 }
79 
80 // Tests a raw subprocess with no protocol.
TEST_F(ShellServiceTest,RawNoProtocolSubprocess)81 TEST_F(ShellServiceTest, RawNoProtocolSubprocess) {
82     // [ -t 0 ] checks if stdin is connected to a terminal.
83     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
84             "echo foo; echo bar >&2; [ -t 0 ]; echo $?",
85             SubprocessType::kRaw, SubprocessProtocol::kNone));
86 
87     // [ -t 0 ] == 0 means we have a terminal (PTY). Even when requesting a raw subprocess, without
88     // the shell protocol we should always force a PTY to ensure proper cleanup.
89     ExpectLinesEqual(ReadRaw(command_fd_), {"foo", "bar", "0"});
90 }
91 
92 // Tests a PTY subprocess with no protocol.
TEST_F(ShellServiceTest,PtyNoProtocolSubprocess)93 TEST_F(ShellServiceTest, PtyNoProtocolSubprocess) {
94     // [ -t 0 ] checks if stdin is connected to a terminal.
95     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
96             "echo foo; echo bar >&2; [ -t 0 ]; echo $?",
97             SubprocessType::kPty, SubprocessProtocol::kNone));
98 
99     // [ -t 0 ] == 0 means we have a terminal (PTY).
100     ExpectLinesEqual(ReadRaw(command_fd_), {"foo", "bar", "0"});
101 }
102 
103 // Tests a raw subprocess with the shell protocol.
TEST_F(ShellServiceTest,RawShellProtocolSubprocess)104 TEST_F(ShellServiceTest, RawShellProtocolSubprocess) {
105     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
106             "echo foo; echo bar >&2; echo baz; exit 24",
107             SubprocessType::kRaw, SubprocessProtocol::kShell));
108 
109     std::string stdout, stderr;
110     EXPECT_EQ(24, ReadShellProtocol(command_fd_, &stdout, &stderr));
111     ExpectLinesEqual(stdout, {"foo", "baz"});
112     ExpectLinesEqual(stderr, {"bar"});
113 }
114 
115 // Tests a PTY subprocess with the shell protocol.
TEST_F(ShellServiceTest,PtyShellProtocolSubprocess)116 TEST_F(ShellServiceTest, PtyShellProtocolSubprocess) {
117     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
118             "echo foo; echo bar >&2; echo baz; exit 50",
119             SubprocessType::kPty, SubprocessProtocol::kShell));
120 
121     // PTY always combines stdout and stderr but the shell protocol should
122     // still give us an exit code.
123     std::string stdout, stderr;
124     EXPECT_EQ(50, ReadShellProtocol(command_fd_, &stdout, &stderr));
125     ExpectLinesEqual(stdout, {"foo", "bar", "baz"});
126     ExpectLinesEqual(stderr, {});
127 }
128 
129 // Tests an interactive PTY session.
TEST_F(ShellServiceTest,InteractivePtySubprocess)130 TEST_F(ShellServiceTest, InteractivePtySubprocess) {
131     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
132             "", SubprocessType::kPty, SubprocessProtocol::kShell));
133 
134     // Use variable substitution so echoed input is different from output.
135     const char* commands[] = {"TEST_STR=abc123",
136                               "echo --${TEST_STR}--",
137                               "exit"};
138 
139     ShellProtocol* protocol = new ShellProtocol(command_fd_);
140     for (std::string command : commands) {
141         // Interactive shell requires a newline to complete each command.
142         command.push_back('\n');
143         memcpy(protocol->data(), command.data(), command.length());
144         ASSERT_TRUE(protocol->Write(ShellProtocol::kIdStdin, command.length()));
145     }
146     delete protocol;
147 
148     std::string stdout, stderr;
149     EXPECT_EQ(0, ReadShellProtocol(command_fd_, &stdout, &stderr));
150     // An unpredictable command prompt makes parsing exact output difficult but
151     // it should at least contain echoed input and the expected output.
152     for (const char* command : commands) {
153         EXPECT_FALSE(stdout.find(command) == std::string::npos);
154     }
155     EXPECT_FALSE(stdout.find("--abc123--") == std::string::npos);
156 }
157 
158 // Tests closing raw subprocess stdin.
TEST_F(ShellServiceTest,CloseClientStdin)159 TEST_F(ShellServiceTest, CloseClientStdin) {
160     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
161             "cat; echo TEST_DONE",
162             SubprocessType::kRaw, SubprocessProtocol::kShell));
163 
164     std::string input = "foo\nbar";
165     ShellProtocol* protocol = new ShellProtocol(command_fd_);
166     memcpy(protocol->data(), input.data(), input.length());
167     ASSERT_TRUE(protocol->Write(ShellProtocol::kIdStdin, input.length()));
168     ASSERT_TRUE(protocol->Write(ShellProtocol::kIdCloseStdin, 0));
169     delete protocol;
170 
171     std::string stdout, stderr;
172     EXPECT_EQ(0, ReadShellProtocol(command_fd_, &stdout, &stderr));
173     ExpectLinesEqual(stdout, {"foo", "barTEST_DONE"});
174     ExpectLinesEqual(stderr, {});
175 }
176 
177 // Tests that nothing breaks when the stdin/stdout pipe closes.
TEST_F(ShellServiceTest,CloseStdinStdoutSubprocess)178 TEST_F(ShellServiceTest, CloseStdinStdoutSubprocess) {
179     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
180             "exec 0<&-; exec 1>&-; echo bar >&2",
181             SubprocessType::kRaw, SubprocessProtocol::kShell));
182 
183     std::string stdout, stderr;
184     EXPECT_EQ(0, ReadShellProtocol(command_fd_, &stdout, &stderr));
185     ExpectLinesEqual(stdout, {});
186     ExpectLinesEqual(stderr, {"bar"});
187 }
188 
189 // Tests that nothing breaks when the stderr pipe closes.
TEST_F(ShellServiceTest,CloseStderrSubprocess)190 TEST_F(ShellServiceTest, CloseStderrSubprocess) {
191     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
192             "exec 2>&-; echo foo",
193             SubprocessType::kRaw, SubprocessProtocol::kShell));
194 
195     std::string stdout, stderr;
196     EXPECT_EQ(0, ReadShellProtocol(command_fd_, &stdout, &stderr));
197     ExpectLinesEqual(stdout, {"foo"});
198     ExpectLinesEqual(stderr, {});
199 }
200 
201 // Tests an inprocess command with no protocol.
TEST_F(ShellServiceTest,RawNoProtocolInprocess)202 TEST_F(ShellServiceTest, RawNoProtocolInprocess) {
203     ASSERT_NO_FATAL_FAILURE(
204             StartTestCommandInProcess("123",
205                                       [](auto args, auto in, auto out, auto err) -> int {
206                                           EXPECT_EQ("123", args);
207                                           char input[10];
208                                           EXPECT_TRUE(ReadFdExactly(in, input, 2));
209                                           input[2] = 0;
210                                           EXPECT_STREQ("in", input);
211                                           WriteFdExactly(out, "out\n");
212                                           WriteFdExactly(err, "err\n");
213                                           return 0;
214                                       },
215                                       SubprocessProtocol::kNone));
216 
217     WriteFdExactly(command_fd_, "in");
218     ExpectLinesEqual(ReadRaw(command_fd_), {"out", "err"});
219 }
220 
221 // Tests an inprocess command with the shell protocol.
TEST_F(ShellServiceTest,RawShellProtocolInprocess)222 TEST_F(ShellServiceTest, RawShellProtocolInprocess) {
223     ASSERT_NO_FATAL_FAILURE(
224             StartTestCommandInProcess("321",
225                                       [](auto args, auto in, auto out, auto err) -> int {
226                                           EXPECT_EQ("321", args);
227                                           char input[10];
228                                           EXPECT_TRUE(ReadFdExactly(in, input, 2));
229                                           input[2] = 0;
230                                           EXPECT_STREQ("in", input);
231                                           WriteFdExactly(out, "out\n");
232                                           WriteFdExactly(err, "err\n");
233                                           return 0;
234                                       },
235                                       SubprocessProtocol::kShell));
236 
237     {
238         auto write_protocol = std::make_unique<ShellProtocol>(command_fd_);
239         memcpy(write_protocol->data(), "in", 2);
240         write_protocol->Write(ShellProtocol::kIdStdin, 2);
241     }
242 
243     std::string stdout, stderr;
244     // For in-process commands the exit code is always the default (1).
245     EXPECT_EQ(1, ReadShellProtocol(command_fd_, &stdout, &stderr));
246     ExpectLinesEqual(stdout, {"out"});
247     ExpectLinesEqual(stderr, {"err"});
248 }
249