1 /*
2  * Copyright 2016 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 #define LOG_TAG "VtsShellDriver"
17 
18 #include "ShellDriver.h"
19 
20 #include <sys/socket.h>
21 #include <sys/stat.h>
22 #include <sys/un.h>
23 #include <sstream>
24 
25 #include <VtsDriverCommUtil.h>
26 #include <VtsDriverFileUtil.h>
27 #include <android-base/logging.h>
28 
29 #include "test/vts/proto/VtsDriverControlMessage.pb.h"
30 
31 using namespace std;
32 
33 // Threshold of serialized proto msg size sent over socket.
34 static constexpr long kProtoSizeThreshold = 1024 * 1024;  // 1MB
35 
36 namespace android {
37 namespace vts {
38 
Close()39 int VtsShellDriver::Close() {
40   int result = 0;
41 
42   if (!this->socket_address_.empty()) {
43     result = unlink(this->socket_address_.c_str());
44     if (result != 0) {
45       LOG(ERROR) << " ERROR closing socket (errno = " << errno << ")";
46     }
47     this->socket_address_.clear();
48   }
49 
50   return result;
51 }
52 
ExecShellCommandPopen(const string & command)53 CommandResult* VtsShellDriver::ExecShellCommandPopen(const string& command) {
54   CommandResult* result = new CommandResult();
55 
56   // TODO: handle no output case.
57   FILE* output_fp;
58 
59   // execute the command.
60   output_fp = popen(command.c_str(), "r");
61   if (output_fp == NULL) {
62     LOG(ERROR) << "Failed to run command: " << command;
63     result->exit_code = errno;
64     return result;
65   }
66 
67   char buff[4096];
68   stringstream ss;
69 
70   int bytes_read;
71   while (!feof(output_fp)) {
72     bytes_read = fread(buff, 1, sizeof(buff) - 1, output_fp);
73     // TODO: catch stderr
74     if (ferror(output_fp)) {
75       LOG(ERROR) << "ERROR reading shell output";
76       result->exit_code = -1;
77       return result;
78     }
79 
80     buff[bytes_read] = '\0';
81     ss << buff;
82   }
83 
84   LOG(DEBUG) << " Returning output: " << ss.str();
85   result->stdout = ss.str();
86 
87   result->exit_code = pclose(output_fp) / 256;
88   return result;
89 }
90 
ExecShellCommandNohup(const string & command)91 CommandResult* VtsShellDriver::ExecShellCommandNohup(const string& command) {
92   CommandResult* result = new CommandResult();
93 
94   string temp_dir = GetDirFromFilePath(this->socket_address_);
95   string temp_file_name_pattern = temp_dir + "/nohupXXXXXX";
96   int temp_file_name_len = temp_file_name_pattern.length() + 1;
97   char stdout_file_name[temp_file_name_len];
98   char stderr_file_name[temp_file_name_len];
99   strcpy(stdout_file_name, temp_file_name_pattern.c_str());
100   strcpy(stderr_file_name, temp_file_name_pattern.c_str());
101   int stdout_file = mkstemp(stdout_file_name);
102   int stderr_file = mkstemp(stderr_file_name);
103   close(stdout_file);
104   close(stderr_file);
105 
106   stringstream ss;
107   ss << "nohup sh -c '" << command << "' >" << stdout_file_name << " 2>"
108      << stderr_file_name;
109 
110   // execute the command.
111   int exit_code = system(ss.str().c_str()) / 256;
112   result->exit_code = exit_code;
113 
114   // If stdout size larger than threshold, send back the temp file path.
115   // Otherwise, send back the context directly.
116   long stdout_size = GetFileSize(stdout_file_name);
117   if (stdout_size > kProtoSizeThreshold) {
118     result->stdout = string(stdout_file_name);
119   } else {
120     result->stdout = ReadFile(stdout_file_name);
121     remove(stdout_file_name);
122   }
123 
124   // If stderr size larger than threshold, send back the temp file path.
125   // Otherwise, send back the context directly.
126   long stderr_size = GetFileSize(stderr_file_name);
127   if (stderr_size > kProtoSizeThreshold) {
128     result->stderr = string(stderr_file_name);
129   } else {
130     result->stderr = ReadFile(stderr_file_name);
131     remove(stderr_file_name);
132   }
133 
134   return result;
135 }
136 
ExecShellCommand(const string & command,VtsDriverControlResponseMessage * responseMessage)137 int VtsShellDriver::ExecShellCommand(
138     const string& command, VtsDriverControlResponseMessage* responseMessage) {
139   CommandResult* result = this->ExecShellCommandNohup(command);
140 
141   responseMessage->add_stdout(result->stdout);
142   responseMessage->add_stderr(result->stderr);
143 
144   int exit_code = result->exit_code;
145   responseMessage->add_exit_code(result->exit_code);
146 
147   delete result;
148   return exit_code;
149 }
150 
HandleShellCommandConnection(int connection_fd)151 int VtsShellDriver::HandleShellCommandConnection(int connection_fd) {
152   VtsDriverCommUtil driverUtil(connection_fd);
153   VtsDriverControlCommandMessage cmd_msg;
154   int numberOfFailure = 0;
155 
156   while (1) {
157     if (!driverUtil.VtsSocketRecvMessage(
158             static_cast<google::protobuf::Message*>(&cmd_msg))) {
159       LOG(ERROR) << "Receiving message failure.";
160       return -1;
161     }
162 
163     if (cmd_msg.command_type() == EXIT) {
164       LOG(ERROR) << "Received exit command.";
165       break;
166     } else if (cmd_msg.command_type() != EXECUTE_COMMAND) {
167       LOG(ERROR) << "Unknown command type " << cmd_msg.command_type();
168       continue;
169     }
170     LOG(INFO) << "Received " << cmd_msg.shell_command_size()
171               << " command(s). Processing...";
172 
173     // execute command and write back output
174     VtsDriverControlResponseMessage responseMessage;
175 
176     for (const auto& command : cmd_msg.shell_command()) {
177       if (ExecShellCommand(command, &responseMessage) != 0) {
178         LOG(ERROR) << "Error during executing command [" << command << "]";
179         --numberOfFailure;
180       }
181     }
182 
183     // TODO: other response code conditions
184     responseMessage.set_response_code(VTS_DRIVER_RESPONSE_SUCCESS);
185     if (!driverUtil.VtsSocketSendMessage(responseMessage)) {
186       LOG(ERROR) << "Write output to socket error.";
187       --numberOfFailure;
188     }
189     LOG(DEBUG) << "Finished processing commands.";
190   }
191 
192   if (driverUtil.Close() != 0) {
193     LOG(ERROR) << "Failed to close connection. errno: " << errno;
194     --numberOfFailure;
195   }
196 
197   return numberOfFailure;
198 }
199 
StartListen()200 int VtsShellDriver::StartListen() {
201   if (this->socket_address_.empty()) {
202     LOG(ERROR) << "NULL socket address.";
203     return -1;
204   }
205 
206   LOG(INFO) << "Start listening on " << this->socket_address_;
207 
208   struct sockaddr_un address;
209   int socket_fd, connection_fd;
210   socklen_t address_length;
211   pid_t child;
212 
213   socket_fd = socket(PF_UNIX, SOCK_STREAM, 0);
214   if (socket_fd < 0) {
215     PLOG(ERROR) << "socket() failed";
216     return socket_fd;
217   }
218 
219   unlink(this->socket_address_.c_str());
220   memset(&address, 0, sizeof(struct sockaddr_un));
221   address.sun_family = AF_UNIX;
222   strncpy(address.sun_path, this->socket_address_.c_str(),
223           sizeof(address.sun_path) - 1);
224 
225   if (::bind(socket_fd, (struct sockaddr*)&address,
226              sizeof(struct sockaddr_un)) != 0) {
227     PLOG(ERROR) << "bind() failed";
228     return 1;
229   }
230 
231   if (listen(socket_fd, 5) != 0) {
232     PLOG(ERROR) << "listen() failed";
233     return errno;
234   }
235 
236   while (1) {
237     address_length = sizeof(address);
238 
239     // TODO(yuexima) exit message to break loop
240     connection_fd =
241         accept(socket_fd, (struct sockaddr*)&address, &address_length);
242     if (connection_fd == -1) {
243       PLOG(ERROR) << "accept failed";
244       break;
245     }
246 
247     child = fork();
248     if (child == 0) {
249       close(socket_fd);
250       // now inside newly created connection handling process
251       if (HandleShellCommandConnection(connection_fd) != 0) {
252         LOG(ERROR) << "Failed to handle connection.";
253         close(connection_fd);
254         exit(1);
255       }
256       close(connection_fd);
257       exit(0);
258     } else if (child > 0) {
259       close(connection_fd);
260     } else {
261       LOG(ERROR) << "Create child process failed. Exiting...";
262       return (errno);
263     }
264   }
265   close(socket_fd);
266 
267   return 0;
268 }
269 
GetFileSize(const char * filename)270 long VtsShellDriver::GetFileSize(const char* filename) {
271   struct stat stat_buf;
272   int rc = stat(filename, &stat_buf);
273   return rc == 0 ? stat_buf.st_size : -1;
274 }
275 
276 }  // namespace vts
277 }  // namespace android
278