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 "workload.h"
18
19#include <errno.h>
20#include <fcntl.h>
21#include <sys/wait.h>
22#include <unistd.h>
23
24#include <android-base/logging.h>
25
26std::unique_ptr<Workload> Workload::CreateWorkload(const std::vector<std::string>& args) {
27  std::unique_ptr<Workload> workload(new Workload(args));
28  if (workload != nullptr && workload->CreateNewProcess()) {
29    return workload;
30  }
31  return nullptr;
32}
33
34Workload::~Workload() {
35  if (work_pid_ != -1 && work_state_ != NotYetCreateNewProcess) {
36    if (!Workload::WaitChildProcess(false)) {
37      kill(work_pid_, SIGKILL);
38      Workload::WaitChildProcess(true);
39    }
40  }
41  if (start_signal_fd_ != -1) {
42    close(start_signal_fd_);
43  }
44  if (exec_child_fd_ != -1) {
45    close(exec_child_fd_);
46  }
47}
48
49static void ChildProcessFn(std::vector<std::string>& args, int start_signal_fd, int exec_child_fd);
50
51bool Workload::CreateNewProcess() {
52  CHECK_EQ(work_state_, NotYetCreateNewProcess);
53
54  int start_signal_pipe[2];
55  if (pipe2(start_signal_pipe, O_CLOEXEC) != 0) {
56    PLOG(ERROR) << "pipe2() failed";
57    return false;
58  }
59
60  int exec_child_pipe[2];
61  if (pipe2(exec_child_pipe, O_CLOEXEC) != 0) {
62    PLOG(ERROR) << "pipe2() failed";
63    close(start_signal_pipe[0]);
64    close(start_signal_pipe[1]);
65    return false;
66  }
67
68  pid_t pid = fork();
69  if (pid == -1) {
70    PLOG(ERROR) << "fork() failed";
71    close(start_signal_pipe[0]);
72    close(start_signal_pipe[1]);
73    close(exec_child_pipe[0]);
74    close(exec_child_pipe[1]);
75    return false;
76  } else if (pid == 0) {
77    // In child process.
78    close(start_signal_pipe[1]);
79    close(exec_child_pipe[0]);
80    ChildProcessFn(args_, start_signal_pipe[0], exec_child_pipe[1]);
81    _exit(0);
82  }
83  // In parent process.
84  close(start_signal_pipe[0]);
85  close(exec_child_pipe[1]);
86  start_signal_fd_ = start_signal_pipe[1];
87  exec_child_fd_ = exec_child_pipe[0];
88  work_pid_ = pid;
89  work_state_ = NotYetStartNewProcess;
90  return true;
91}
92
93static void ChildProcessFn(std::vector<std::string>& args, int start_signal_fd, int exec_child_fd) {
94  std::vector<char*> argv(args.size() + 1);
95  for (size_t i = 0; i < args.size(); ++i) {
96    argv[i] = &args[i][0];
97  }
98  argv[args.size()] = nullptr;
99
100  char start_signal = 0;
101  ssize_t nread = TEMP_FAILURE_RETRY(read(start_signal_fd, &start_signal, 1));
102  if (nread == 1 && start_signal == 1) {
103    close(start_signal_fd);
104    execvp(argv[0], argv.data());
105    // If execvp() succeed, we will not arrive here. But if it failed, we need to
106    // report the failure to the parent process by writing 1 to exec_child_fd.
107    int saved_errno = errno;
108    char exec_child_failed = 1;
109    TEMP_FAILURE_RETRY(write(exec_child_fd, &exec_child_failed, 1));
110    close(exec_child_fd);
111    errno = saved_errno;
112    PLOG(ERROR) << "child process failed to execvp(" << argv[0] << ")";
113  } else {
114    PLOG(ERROR) << "child process failed to receive start_signal, nread = " << nread;
115  }
116}
117
118bool Workload::Start() {
119  CHECK_EQ(work_state_, NotYetStartNewProcess);
120  char start_signal = 1;
121  ssize_t nwrite = TEMP_FAILURE_RETRY(write(start_signal_fd_, &start_signal, 1));
122  if (nwrite != 1) {
123    PLOG(ERROR) << "write start signal failed";
124    return false;
125  }
126  char exec_child_failed;
127  ssize_t nread = TEMP_FAILURE_RETRY(read(exec_child_fd_, &exec_child_failed, 1));
128  if (nread != 0) {
129    if (nread == -1) {
130      PLOG(ERROR) << "failed to receive error message from child process";
131    } else {
132      LOG(ERROR) << "received error message from child process";
133    }
134    return false;
135  }
136  work_state_ = Started;
137  return true;
138}
139
140bool Workload::WaitChildProcess(bool wait_forever) {
141  bool finished = false;
142  int status;
143  pid_t result = TEMP_FAILURE_RETRY(waitpid(work_pid_, &status, (wait_forever ? 0 : WNOHANG)));
144  if (result == work_pid_) {
145    finished = true;
146    if (WIFSIGNALED(status)) {
147      LOG(WARNING) << "child process was terminated by signal " << strsignal(WTERMSIG(status));
148    } else if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
149      LOG(WARNING) << "child process exited with exit code " << WEXITSTATUS(status);
150    }
151  } else if (result == -1) {
152    PLOG(ERROR) << "waitpid() failed";
153  }
154  return finished;
155}
156