1/*
2 * Copyright (c) 2023 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
16#include "pid_utils.h"
17#include <cerrno>
18#include <cstdint>
19#include <cstdio>
20#include <ctime>
21#include <sys/ptrace.h>
22#include <sys/types.h>
23#include <sys/wait.h>
24#include <unistd.h>
25#include "dfx_log.h"
26
27namespace OHOS {
28namespace HiviewDFX {
29namespace {
30#undef LOG_DOMAIN
31#undef LOG_TAG
32#define LOG_DOMAIN 0xD002D11
33#define LOG_TAG "DfxPidUtils"
34
35static constexpr time_t MAX_WAIT_TIME_SECONDS = 30;
36static constexpr time_t USLEEP_TIME = 5000;
37}
38
39static bool Exited(pid_t pid)
40{
41    int status;
42    pid_t waitPid = waitpid(pid, &status, WNOHANG);
43    if (waitPid != pid) {
44        return false;
45    }
46
47    if (WIFEXITED(status)) {
48        DFXLOGE("%{public}d died: Process exited with code %{public}d", pid, WEXITSTATUS(status));
49    } else if (WIFSIGNALED(status)) {
50        DFXLOGE("%{public}d died: Process exited due to signal %{public}d", pid, WTERMSIG(status));
51    } else {
52        DFXLOGE("%{public}d died: Process finished for unknown reason", pid);
53    }
54    return true;
55}
56
57bool PidUtils::Quiesce(pid_t pid)
58{
59    siginfo_t si;
60    // Wait for up to 10 seconds.
61    for (time_t startTime = time(nullptr); time(nullptr) - startTime < 10;) {
62        if (ptrace(PTRACE_GETSIGINFO, pid, 0, &si) == 0) {
63            return true;
64        }
65        if (errno != ESRCH) {
66            if (errno != EINVAL) {
67                DFXLOGE("ptrace getsiginfo failed");
68                return false;
69            }
70            // The process is in group-stop state, so try and kick the process out of that state.
71            if (ptrace(PTRACE_LISTEN, pid, 0, 0) == -1) {
72                // Cannot recover from this, so just pretend it worked and see if we can unwind.
73                return true;
74            }
75        }
76        usleep(USLEEP_TIME);
77    }
78    DFXLOGE("%{public}d: Did not quiesce in 10 seconds", pid);
79    return false;
80}
81
82bool PidUtils::Attach(pid_t pid)
83{
84    // Wait up to 45 seconds to attach.
85    for (time_t startTime = time(nullptr); time(nullptr) - startTime < 45;) {
86        if (ptrace(PTRACE_ATTACH, pid, 0, 0) == 0) {
87            break;
88        }
89        if (errno != ESRCH) {
90            DFXLOGE("Failed to attach");
91            return false;
92        }
93        usleep(USLEEP_TIME);
94    }
95
96    if (Quiesce(pid)) {
97        return true;
98    }
99
100    if (ptrace(PTRACE_DETACH, pid, 0, 0) == -1) {
101        DFXLOGE("Failed to detach");
102    }
103    return false;
104}
105
106bool PidUtils::Detach(pid_t pid)
107{
108    if (ptrace(PTRACE_DETACH, pid, 0, 0) == -1) {
109        DFXLOGE("ptrace detach failed");
110        return false;
111    }
112    return true;
113}
114
115bool PidUtils::WaitForPidState(pid_t pid, const std::function<PidRunEnum()>& stateCheckFunc)
116{
117    PidRunEnum status = PID_RUN_KEEP_GOING;
118    for (time_t startTime = time(nullptr);
119        time(nullptr) - startTime < MAX_WAIT_TIME_SECONDS && status == PID_RUN_KEEP_GOING;) {
120        if (Attach(pid)) {
121            status = stateCheckFunc();
122            if (status == PID_RUN_PASS) {
123                return true;
124            }
125
126            if (!Detach(pid)) {
127                return false;
128            }
129        } else if (Exited(pid)) {
130            return false;
131        }
132        usleep(USLEEP_TIME);
133    }
134    if (status == PID_RUN_KEEP_GOING) {
135        DFXLOGE("Timed out waiting for pid %{public}d to be ready", pid);
136    }
137    return status == PID_RUN_PASS;
138}
139
140bool PidUtils::WaitForPidStateAfterAttach(pid_t pid, const std::function<PidRunEnum()>& stateCheckFunc)
141{
142    PidRunEnum status;
143    time_t startTime = time(nullptr);
144    do {
145        status = stateCheckFunc();
146        if (status == PID_RUN_PASS) {
147            return true;
148        }
149        if (!Detach(pid)) {
150            return false;
151        }
152        usleep(USLEEP_TIME);
153    } while (time(nullptr) - startTime < MAX_WAIT_TIME_SECONDS && status == PID_RUN_KEEP_GOING && Attach(pid));
154    if (status == PID_RUN_KEEP_GOING) {
155        DFXLOGE("Timed out waiting for pid %{public}d to be ready", pid);
156    }
157    return status == PID_RUN_PASS;
158}
159
160}
161}