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