1/*
2 * Copyright (c) 2021 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15#include "tracked_command.h"
16#include <cerrno>
17#include <csignal>
18#include <sys/prctl.h>
19#include <sys/wait.h>
20#include <unistd.h>
21#include "debug_logger.h"
22
23namespace OHOS {
24namespace Developtools {
25namespace HiPerf {
26std::unique_ptr<TrackedCommand> TrackedCommand::CreateInstance(const std::vector<std::string> &args)
27{
28    std::unique_ptr<TrackedCommand> command {new (std::nothrow) TrackedCommand(args)};
29    if (!command) {
30        return nullptr;
31    }
32    if (!command->CreateChildProcess()) {
33        return nullptr;
34    }
35    return command;
36}
37
38TrackedCommand::TrackedCommand(const std::vector<std::string> &args) : command_ {args} {}
39
40TrackedCommand::~TrackedCommand()
41{
42    MakeInvalid();
43}
44
45void TrackedCommand::Stop()
46{
47    MakeInvalid();
48}
49
50bool TrackedCommand::InitSignalPipes(int &startFd, int &ackFd)
51{
52    int startSignalPipe[2] {-1, -1};
53    if (pipe2(startSignalPipe, O_CLOEXEC) != 0) {
54        HLOGE("pipe2() failed in TrackedCommand::InitSignalPipes()");
55        return false;
56    }
57    startFd = startSignalPipe[0];
58    startFd_ = startSignalPipe[1];
59
60    int ackSignalPipe[2] {-1, -1};
61    if (pipe2(ackSignalPipe, O_CLOEXEC) != 0) {
62        HLOGE("pipe2() failed in TrackedCommand::InitSignalPipes()");
63        close(startFd);
64        close(startFd_);
65        startFd = -1;
66        startFd_ = -1;
67        return false;
68    }
69    ackFd = ackSignalPipe[1];
70    ackFd_ = ackSignalPipe[0];
71    return true;
72}
73
74bool TrackedCommand::CreateChildProcess()
75{
76    int startFd {-1};
77    int ackFd {-1};
78    if (!InitSignalPipes(startFd, ackFd)) {
79        return false;
80    }
81    pid_t pid = fork();
82    if (pid == -1) {
83        HLOGE("fork() failed in TrackedCommand::CreateChildProcess()");
84        MakeInvalid();
85        return false;
86    } else if (pid == 0) {
87        close(startFd_);
88        close(ackFd_);
89        ExecuteCommand(startFd, ackFd);
90        _exit(0);
91    } else {
92        close(startFd);
93        close(ackFd);
94        childPid_ = pid;
95        state_ = State::COMMAND_WAITING;
96        return true;
97    }
98}
99
100bool TrackedCommand::StartCommand()
101{
102    // send start signal to start execution of command
103    ssize_t nbyte {0};
104    char startSignal {1};
105    while (true) {
106        nbyte = write(startFd_, &startSignal, 1);
107        if (nbyte == -1) {
108            continue;
109        }
110        break;
111    }
112    HLOG_ASSERT(nbyte == 1);
113    // check execution state of command
114    // read acknowledgement signal
115    char ackSignal {0};
116    while (true) {
117        nbyte = read(ackFd_, &ackSignal, 1);
118        if (nbyte == -1 and (errno == EINTR or errno == EIO)) {
119            continue;
120        }
121        HLOGE("*** nbyte: %zd, ackSignal: %d ***\n", nbyte, ackSignal);
122        break;
123    }
124    if (nbyte == 0) {
125        state_ = State::COMMAND_STARTED;
126        return true;
127    }
128    HLOG_ASSERT(nbyte == 1);
129    state_ = State::COMMAND_FAILURE;
130    return false;
131}
132
133void TrackedCommand::ExecuteCommand(const int &startFd, const int &ackFd)
134{
135    HLOG_ASSERT(startFd != -1);
136    HLOG_ASSERT(ackFd != -1);
137    prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
138    // waiting start signal
139    char startSignal {0};
140    ssize_t nbyte {0};
141    while (true) {
142        nbyte = read(startFd, &startSignal, 1);
143        if (nbyte == -1) {
144            continue;
145        }
146        break;
147    }
148    HLOG_ASSERT(nbyte == 1);
149    // execute command
150    char *argv[command_.size() + 1];
151    for (size_t index = 0; index < command_.size(); ++index) {
152        argv[index] = const_cast<char *>(command_[index].c_str());
153    }
154    argv[command_.size()] = nullptr;
155    // On sucees, startFd and ackFd will be closed hence parent process reads EPIPE;
156    if (IsPath(argv[0])) {
157        execv(argv[0], argv);
158    } else {
159        execvp(argv[0], argv);
160    }
161    // execv() or execvp() failed, send failure signal
162    char ackSignal {1};
163    while (true) {
164        nbyte = write(ackFd, &ackSignal, 1);
165        if (nbyte == -1) {
166            continue;
167        }
168        break;
169    }
170    HLOG_ASSERT(nbyte == 1);
171    HLOGE("child process failed to execute command");
172}
173
174bool TrackedCommand::WaitCommand(int &wstatus)
175{
176    if (childPid_ != -1) {
177        HLOG_ASSERT(state_ != State::COMMAND_STOPPED);
178        pid_t pid = waitpid(childPid_, &wstatus, WNOHANG);
179        if (pid == 0) {
180            return false;
181        } else { // pid == childPid_ or pid == -1
182            childPid_ = -1;
183            state_ = State::COMMAND_STOPPED;
184            return true;
185        }
186    }
187    return true;
188}
189
190void TrackedCommand::MakeInvalid()
191{
192    if (childPid_ != -1) {
193        HLOG_ASSERT(state_ != State::COMMAND_STOPPED);
194        int wstatus;
195        pid_t pid = waitpid(childPid_, &wstatus, WNOHANG);
196        if (pid != childPid_) {
197            kill(childPid_, SIGKILL);
198        }
199        childPid_ = -1;
200        state_ = State::COMMAND_STOPPED;
201    }
202    if (startFd_ != -1) {
203        close(startFd_);
204        startFd_ = -1;
205    }
206    if (ackFd_ != -1) {
207        close(ackFd_);
208        ackFd_ = -1;
209    }
210}
211} // namespace HiPerf
212} // namespace Developtools
213} // namespace OHOS
214