Home | History | Annotate | Download | only in shell
      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 
     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 
     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 
     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 
    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 
    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 
    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     LOG(ERROR) << "Socket() failed: " << strerror(errno);
    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     LOG(ERROR) << "bind() failed: " << strerror(errno);
    228     return 1;
    229   }
    230 
    231   if (listen(socket_fd, 5) != 0) {
    232     LOG(ERROR) << "listen() failed: " << strerror(errno);
    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       LOG(ERROR) << "Accept error: " << strerror(errno);
    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 
    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