/* * Copyright 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "ShellDriver.h" #include <stdio.h> #include <stdlib.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/un.h> #include <unistd.h> #include <iostream> #include <sstream> #include <VtsDriverCommUtil.h> #include <VtsDriverFileUtil.h> #include "test/vts/proto/VtsDriverControlMessage.pb.h" using namespace std; namespace android { namespace vts { int VtsShellDriver::Close() { cout << __func__ << endl; int result = 0; if (!this->socket_address_.empty()) { result = unlink(this->socket_address_.c_str()); if (result != 0) { cerr << __func__ << ":" << __LINE__ << " ERROR closing socket (errno = " << errno << ")" << endl; } this->socket_address_.clear(); } return result; } CommandResult* VtsShellDriver::ExecShellCommandPopen(const string& command) { CommandResult* result = new CommandResult(); // TODO: handle no output case. FILE* output_fp; cout << "[Driver] Running command: " << command << endl << endl; // execute the command. output_fp = popen(command.c_str(), "r"); if (output_fp == NULL) { cerr << "Failed to run command: " << command << endl; result->exit_code = errno; return result; } char buff[4096]; stringstream ss; int bytes_read; while (!feof(output_fp)) { bytes_read = fread(buff, 1, sizeof(buff) - 1, output_fp); // TODO: catch stderr if (ferror(output_fp)) { cerr << __func__ << ":" << __LINE__ << "ERROR reading shell output" << endl; result->exit_code = -1; return result; } cout << "[Driver] bytes read from output: " << bytes_read << endl; buff[bytes_read] = '\0'; ss << buff; } cout << "[Driver] Returning output: " << ss.str() << endl << endl; result->stdout = ss.str(); result->exit_code = pclose(output_fp) / 256; return result; } CommandResult* VtsShellDriver::ExecShellCommandNohup(const string& command) { CommandResult* result = new CommandResult(); string temp_dir = GetDirFromFilePath(this->socket_address_); string temp_file_name_pattern = temp_dir + "/nohupXXXXXX"; int temp_file_name_len = temp_file_name_pattern.length() + 1; char stdout_file_name[temp_file_name_len]; char stderr_file_name[temp_file_name_len]; strcpy(stdout_file_name, temp_file_name_pattern.c_str()); strcpy(stderr_file_name, temp_file_name_pattern.c_str()); int stdout_file = mkstemp(stdout_file_name); int stderr_file = mkstemp(stderr_file_name); close(stdout_file); close(stderr_file); stringstream ss; ss << "nohup sh -c '" << command << "' >" << stdout_file_name << " 2>" << stderr_file_name; // execute the command. int exit_code = system(ss.str().c_str()) / 256; result->exit_code = exit_code; result->stdout = ReadFile(stdout_file_name); result->stderr = ReadFile(stderr_file_name); remove(stdout_file_name); remove(stderr_file_name); return result; } int VtsShellDriver::ExecShellCommand( const string& command, VtsDriverControlResponseMessage* responseMessage) { CommandResult* result = this->ExecShellCommandNohup(command); responseMessage->add_stdout(result->stdout); responseMessage->add_stderr(result->stderr); int exit_code = result->exit_code; responseMessage->add_exit_code(result->exit_code); delete result; return exit_code; } int VtsShellDriver::HandleShellCommandConnection(int connection_fd) { VtsDriverCommUtil driverUtil(connection_fd); VtsDriverControlCommandMessage cmd_msg; int numberOfFailure = 0; while (1) { if (!driverUtil.VtsSocketRecvMessage( static_cast<google::protobuf::Message*>(&cmd_msg))) { cerr << "[Shell driver] receiving message failure." << endl; return -1; } if (cmd_msg.command_type() == EXIT) { cout << "[Shell driver] received exit command." << endl; break; } else if (cmd_msg.command_type() != EXECUTE_COMMAND) { cerr << "[Shell driver] unknown command type " << cmd_msg.command_type() << endl; continue; } cout << "[Shell driver] received " << cmd_msg.shell_command_size() << " command(s). Processing... " << endl; // execute command and write back output VtsDriverControlResponseMessage responseMessage; for (const auto& command : cmd_msg.shell_command()) { if (ExecShellCommand(command, &responseMessage) != 0) { cerr << "[Shell driver] error during executing command [" << command << "]" << endl; --numberOfFailure; } } // TODO: other response code conditions responseMessage.set_response_code(VTS_DRIVER_RESPONSE_SUCCESS); if (!driverUtil.VtsSocketSendMessage(responseMessage)) { fprintf(stderr, "Driver: write output to socket error.\n"); --numberOfFailure; } cout << "[Shell driver] finished processing commands." << endl; } if (driverUtil.Close() != 0) { cerr << "[Driver] failed to close connection. errno: " << errno << endl; --numberOfFailure; } return numberOfFailure; } int VtsShellDriver::StartListen() { if (this->socket_address_.empty()) { cerr << "[Driver] NULL socket address." << endl; return -1; } cout << "[Driver] start listening on " << this->socket_address_ << endl; struct sockaddr_un address; int socket_fd, connection_fd; socklen_t address_length; pid_t child; socket_fd = socket(PF_UNIX, SOCK_STREAM, 0); if (socket_fd < 0) { cerr << "Driver: socket() failed: " << strerror(errno) << endl; return socket_fd; } unlink(this->socket_address_.c_str()); memset(&address, 0, sizeof(struct sockaddr_un)); address.sun_family = AF_UNIX; strncpy(address.sun_path, this->socket_address_.c_str(), sizeof(address.sun_path) - 1); if (::bind(socket_fd, (struct sockaddr*)&address, sizeof(struct sockaddr_un)) != 0) { cerr << "Driver: bind() failed: " << strerror(errno) << endl; return 1; } if (listen(socket_fd, 5) != 0) { cerr << "Driver: listen() failed: " << strerror(errno) << endl; return errno; } while (1) { address_length = sizeof(address); // TODO(yuexima) exit message to break loop connection_fd = accept(socket_fd, (struct sockaddr*)&address, &address_length); if (connection_fd == -1) { cerr << "Driver: accept error: " << strerror(errno) << endl; break; } child = fork(); if (child == 0) { close(socket_fd); // now inside newly created connection handling process if (HandleShellCommandConnection(connection_fd) != 0) { cerr << "[Driver] failed to handle connection." << endl; close(connection_fd); exit(1); } close(connection_fd); exit(0); } else if (child > 0) { close(connection_fd); } else { cerr << "[Driver] create child process failed. Exiting..." << endl; return (errno); } } close(socket_fd); return 0; } } // namespace vts } // namespace android