1/*
2 * Copyright (c) 2022 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 "js_childprocess.h"
17
18#include <map>
19#include <vector>
20
21#include <csignal>
22#include <cstdlib>
23#include <sys/stat.h>
24#include <unistd.h>
25#include <sys/wait.h>
26
27#include "securec.h"
28#include "tools/log.h"
29
30namespace OHOS::JsSysModule::Process {
31    constexpr int MAXSIZE = 1024;
32    constexpr int TIME_EXCHANGE = 1000;
33    std::map<std::string, int> g_signalsMap = {
34        {"SIGHUP", 1},
35        {"SIGINT", 2},
36        {"SIGQUIT", 3},
37        {"SIGILL", 4},
38        {"SIGTRAP", 5},
39        {"SIGABRT", 6},
40        {"SIGBUS", 7},
41        {"SIGFPE", 8},
42        {"SIGKILL", 9},
43        {"SIGUSR1", 10},
44        {"SIGSEGV", 11},
45        {"SIGUSR2", 12},
46        {"SIGPIPE", 13},
47        {"SIGALRM", 14},
48        {"SIGTERM", 15},
49        {"SIGSTKFLT", 16},
50        {"SIGCHLD", 17},
51        {"SIGCONT", 18},
52        {"SIGSTOP", 19},
53        {"SIGTSTP", 20},
54        {"SIGTTIN", 21},
55        {"SIGTTOU", 22},
56        {"SIGURG", 23},
57        {"SIGXCPU", 24},
58        {"SIGXFSZ", 25},
59        {"SIGVTALRM", 26},
60        {"SIGPROF", 27},
61        {"SIGWINCH", 28},
62        {"SIGIO", 29},
63        {"SIGPWR", 30},
64        {"SIGSYS", 31}
65    };
66
67    void ChildProcess::Spawn(napi_env env, napi_value command)
68    {
69        int ret = pipe(stdOutFd_);
70        if (ret < 0) {
71            HILOG_ERROR("pipe1 failed %{public}d", errno);
72            return;
73        }
74        ret = pipe(stdErrFd_);
75        if (ret < 0) {
76            HILOG_ERROR("pipe2 failed %{public}d", errno);
77            return;
78        }
79        std::string strCommnd = RequireStrValue(env, command);
80        pid_t pid = fork();
81        if (!pid) {
82            close(stdErrFd_[0]);
83            close(stdOutFd_[0]);
84            dup2(stdOutFd_[1], 1);
85            dup2(stdErrFd_[1], 2); // 2:The value of parameter
86            if (execl("/bin/sh", "sh", "-c", strCommnd.c_str(), nullptr) == -1) {
87                HILOG_ERROR("execl command failed");
88                _exit(127); // 127:The parameter value
89            }
90        } else if (pid > 0) {
91            if (optionsInfo_ == nullptr) {
92                HILOG_ERROR("optionsInfo_ is nullptr");
93                return;
94            }
95            optionsInfo_->pid = pid;
96            ppid_ = getpid();
97            CreateWorker(env);
98            napi_value resourceName = nullptr;
99            napi_create_string_utf8(env, "TimeoutListener", strlen("TimeoutListener"), &resourceName);
100            napi_create_async_work(
101                env, nullptr, resourceName, TimeoutListener,
102                [](napi_env env, napi_status status, void* data) {
103                    OptionsInfo* optionsInfo = reinterpret_cast<OptionsInfo*>(data);
104                    napi_delete_async_work(env, optionsInfo->worker);
105                    delete optionsInfo;
106                    optionsInfo = nullptr;
107                },
108                reinterpret_cast<void*>(optionsInfo_), &optionsInfo_->worker);
109            napi_queue_async_work_with_qos(env, optionsInfo_->worker, napi_qos_user_initiated);
110            close(stdErrFd_[1]);
111            close(stdOutFd_[1]);
112        } else {
113            HILOG_ERROR("child process create failed");
114        }
115    }
116
117    napi_value ChildProcess::Wait(napi_env env)
118    {
119        napi_value promise = nullptr;
120        auto waitInfo = new WaitInfo;
121        napi_create_promise(env, &(waitInfo->deferred), &promise);
122
123        if (isWait_) {
124            int32_t status;
125            isWait_ = false;
126            if (optionsInfo_ == nullptr) {
127                napi_value res = nullptr;
128                napi_get_undefined(env, &res);
129                delete waitInfo;
130                waitInfo = nullptr;
131                HILOG_ERROR("optionsInfo_ is nullptr");
132                return res;
133            }
134            waitpid(optionsInfo_->pid, &status, 0);
135            exitCode_ = status;
136        }
137        isNeedRun_ = false;
138        napi_value result = nullptr;
139        napi_create_int32(env, static_cast<int8_t>(exitCode_), &result);
140        napi_resolve_deferred(env, waitInfo->deferred, result);
141        delete waitInfo;
142        waitInfo = nullptr;
143
144        return promise;
145    }
146
147    napi_value ChildProcess::GetOutput(napi_env env) const
148    {
149        if (stdOutInfo_ == nullptr) {
150            napi_value res = nullptr;
151            NAPI_CALL(env, napi_get_undefined(env, &res));
152            HILOG_ERROR("stdOutInfo_ is nullptr");
153            return res;
154        }
155        NAPI_CALL(env, napi_create_promise(env, &stdOutInfo_->deferred, &stdOutInfo_->promise));
156        void* data = nullptr;
157        napi_value arrayBuffer = nullptr;
158        size_t bufferSize = stdOutInfo_->stdData.size() + 1;
159        NAPI_CALL(env, napi_create_arraybuffer(env, bufferSize, &data, &arrayBuffer));
160        if (memcpy_s(data, bufferSize, reinterpret_cast<const void*>(stdOutInfo_->stdData.c_str()),
161            stdOutInfo_->stdData.size()) != EOK) {
162            HILOG_ERROR("getOutput memcpy_s failed");
163            NAPI_CALL(env, napi_delete_async_work(env, stdOutInfo_->worker));
164            napi_value res = nullptr;
165            NAPI_CALL(env, napi_get_undefined(env, &res));
166            return res;
167        }
168
169        napi_value result = nullptr;
170        NAPI_CALL(env, napi_create_typedarray(env, napi_uint8_array, bufferSize, arrayBuffer, 0, &result));
171        NAPI_CALL(env, napi_resolve_deferred(env, stdOutInfo_->deferred, result));
172        return stdOutInfo_->promise;
173    }
174
175    napi_value ChildProcess::GetErrorOutput(napi_env env) const
176    {
177        if (stdErrInfo_ == nullptr) {
178            napi_value res = nullptr;
179            NAPI_CALL(env, napi_get_undefined(env, &res));
180            HILOG_ERROR("stdErrInfo_ is nullptr");
181            return res;
182        }
183        NAPI_CALL(env, napi_create_promise(env, &stdErrInfo_->deferred, &stdErrInfo_->promise));
184        void* data = nullptr;
185        napi_value arrayBuffer = nullptr;
186        size_t bufferSize = stdErrInfo_->stdData.size() + 1;
187        NAPI_CALL(env, napi_create_arraybuffer(env, bufferSize, &data, &arrayBuffer));
188        if (memcpy_s(data, bufferSize, reinterpret_cast<const void*>(stdErrInfo_->stdData.c_str()),
189            stdErrInfo_->stdData.size()) != EOK) {
190            HILOG_ERROR("getErrOutput memcpy_s failed");
191            NAPI_CALL(env, napi_delete_async_work(env, stdErrInfo_->worker));
192            napi_value res = nullptr;
193            NAPI_CALL(env, napi_get_undefined(env, &res));
194            return res;
195        }
196
197        napi_value result = nullptr;
198        NAPI_CALL(env, napi_create_typedarray(env, napi_uint8_array, bufferSize, arrayBuffer, 0, &result));
199        NAPI_CALL(env, napi_resolve_deferred(env, stdErrInfo_->deferred, result));
200        return stdErrInfo_->promise;
201    }
202
203    napi_value ChildProcess::GetKilled(napi_env env) const
204    {
205        napi_value result = nullptr;
206        NAPI_CALL(env, napi_get_boolean(env, killed_, &result));
207
208        return result;
209    }
210
211    napi_value ChildProcess::Getpid(napi_env env) const
212    {
213        napi_value result = nullptr;
214        if (optionsInfo_ == nullptr) {
215            napi_value res = nullptr;
216            NAPI_CALL(env, napi_get_undefined(env, &res));
217            HILOG_ERROR("optionsInfo_ is nullptr");
218            return res;
219        }
220        NAPI_CALL(env, napi_create_int32(env, optionsInfo_->pid, &result));
221
222        return result;
223    }
224
225    napi_value ChildProcess::Getppid(napi_env env) const
226    {
227        napi_value result = nullptr;
228        NAPI_CALL(env, napi_create_int32(env, ppid_, &result));
229
230        return result;
231    }
232
233    napi_value ChildProcess::GetExitCode(napi_env env) const
234    {
235        napi_value result = nullptr;
236        NAPI_CALL(env, napi_create_int32(env, static_cast<int8_t>(exitCode_), &result));
237
238        return result;
239    }
240
241    void ChildProcess::CreateWorker(napi_env env)
242    {
243        // getstdout
244        napi_value resourceName = nullptr;
245        stdOutInfo_ = new StdInfo();
246        if (stdOutInfo_ == nullptr) {
247            HILOG_ERROR("stdOutInfo_ is nullptr");
248            return;
249        }
250        stdOutInfo_->isNeedRun = &isNeedRun_;
251        stdOutInfo_->fd = stdOutFd_[0];
252        if (optionsInfo_ == nullptr) {
253            HILOG_ERROR("optionsInfo_ is nullptr");
254            return;
255        }
256        stdOutInfo_->pid = optionsInfo_->pid;
257        stdOutInfo_->maxBuffSize = optionsInfo_->maxBuffer;
258        napi_create_string_utf8(env, "ReadStdOut", NAPI_AUTO_LENGTH, &resourceName);
259        napi_create_async_work(env, nullptr, resourceName, ReadStdOut, EndStdOut,
260                               reinterpret_cast<void*>(stdOutInfo_), &stdOutInfo_->worker);
261        napi_queue_async_work_with_qos(env, stdOutInfo_->worker, napi_qos_user_initiated);
262
263        // getstderr
264        stdErrInfo_ = new StdInfo();
265        if (stdErrInfo_ == nullptr) {
266            HILOG_ERROR("stdErrInfo_ is nullptr");
267            return;
268        }
269        stdErrInfo_->isNeedRun = &isNeedRun_;
270        stdErrInfo_->fd = stdErrFd_[0];
271        stdErrInfo_->pid = optionsInfo_->pid;
272        stdErrInfo_->maxBuffSize = optionsInfo_->maxBuffer;
273        napi_create_string_utf8(env, "ReadStdErr", NAPI_AUTO_LENGTH, &resourceName);
274        napi_create_async_work(env, nullptr, resourceName, ReadStdErr, EndStdErr,
275                               reinterpret_cast<void*>(stdErrInfo_), &stdErrInfo_->worker);
276        napi_queue_async_work_with_qos(env, stdErrInfo_->worker, napi_qos_user_initiated);
277    }
278
279    void ChildProcess::ReadStdOut(napi_env env, void* data)
280    {
281        auto stdOutInfo = reinterpret_cast<StdInfo*>(data);
282        char childStdout[MAXSIZE] = {0};
283        if (stdOutInfo->isNeedRun == nullptr) {
284            return;
285        }
286        while (*(stdOutInfo->isNeedRun)) {
287            auto readSize = read(stdOutInfo->fd, childStdout, sizeof(childStdout) - 1);
288            if (readSize >= 0) {
289                stdOutInfo->stdData += childStdout;
290            }
291            if (stdOutInfo->stdData.size() > static_cast<size_t>(stdOutInfo->maxBuffSize) && *(stdOutInfo->isNeedRun)) {
292                if (!kill(stdOutInfo->pid, SIGKILL)) {
293                    *(stdOutInfo->isNeedRun) = false;
294                    stdOutInfo->stdData = stdOutInfo->stdData.substr(0, stdOutInfo->maxBuffSize);
295                } else {
296                    HILOG_ERROR("stdOut maxBuff kill signal failed");
297                }
298            }
299            if (memset_s(childStdout, sizeof(childStdout), '\0', MAXSIZE) != EOK) {
300                HILOG_ERROR("getOutput memset_s failed");
301                return;
302            }
303        }
304    }
305
306    void ChildProcess::EndStdOut(napi_env env, napi_status status, void* buffer)
307    {
308        auto stdOutInfo = reinterpret_cast<StdInfo*>(buffer);
309        napi_delete_async_work(env, stdOutInfo->worker);
310        delete stdOutInfo;
311        stdOutInfo = nullptr;
312    }
313
314    void ChildProcess::ReadStdErr(napi_env env, void* data)
315    {
316        auto stdErrInfo = reinterpret_cast<StdInfo*>(data);
317        char childStderr[MAXSIZE] = {0};
318        if (stdErrInfo->isNeedRun == nullptr) {
319            return;
320        }
321        while (*(stdErrInfo->isNeedRun)) {
322            auto readSize = read(stdErrInfo->fd, childStderr, sizeof(childStderr) - 1);
323            if (readSize >= 0) {
324                stdErrInfo->stdData += childStderr;
325            }
326            if (stdErrInfo->stdData.size() > static_cast<size_t>(stdErrInfo->maxBuffSize) && *(stdErrInfo->isNeedRun)) {
327                if (!kill(stdErrInfo->pid, SIGKILL)) {
328                    *(stdErrInfo->isNeedRun) = false;
329                    stdErrInfo->stdData = stdErrInfo->stdData.substr(0, stdErrInfo->maxBuffSize);
330                } else {
331                    HILOG_ERROR("stdErr maxBuff kill signal failed");
332                }
333            }
334            if (memset_s(childStderr, sizeof(childStderr), '\0', MAXSIZE) != EOK) {
335                HILOG_ERROR("getOutput memset_s failed");
336                return;
337            }
338        }
339    }
340
341    void ChildProcess::EndStdErr(napi_env env, napi_status status, void* buffer)
342    {
343        auto stdErrInfo = reinterpret_cast<StdInfo*>(buffer);
344        napi_delete_async_work(env, stdErrInfo->worker);
345        delete stdErrInfo;
346        stdErrInfo = nullptr;
347    }
348
349    int ChildProcess::GetValidSignal(napi_env env, const napi_value signo)
350    {
351        int32_t sig = 0;
352        napi_valuetype valuetype = napi_undefined;
353        napi_typeof(env, signo, &valuetype);
354        if (valuetype == napi_valuetype::napi_number) {
355            napi_get_value_int32(env, signo, &sig);
356            return sig;
357        } else if (valuetype == napi_valuetype::napi_string) {
358            std::string buffer = RequireStrValue(env, signo);
359            auto iter = g_signalsMap.find(buffer);
360            if (iter != g_signalsMap.end()) {
361                sig = iter->second;
362                return sig;
363            } else {
364                return g_signalsMap["SIGTERM"];
365            }
366        } else {
367            return g_signalsMap["SIGTERM"];
368        }
369    }
370
371    void ChildProcess::Kill(napi_env env, const napi_value signo)
372    {
373        int signal = GetValidSignal(env, signo);
374        std::vector<int32_t> signalType = {SIGINT, SIGQUIT, SIGKILL, SIGTERM};
375        if (optionsInfo_ == nullptr) {
376            HILOG_ERROR("optionsInfo_ is nullptr");
377            return;
378        }
379        if (!kill(optionsInfo_->pid, signal)) {
380            auto res = std::find(signalType.begin(), signalType.end(), static_cast<int32_t>(signal));
381            (res != signalType.end()) ? isNeedRun_ = false : 0;
382            killed_ = true;
383        } else {
384            HILOG_ERROR("kill signal failed");
385        }
386    }
387
388    void ChildProcess::Close()
389    {
390        int32_t status = 0;
391        if (optionsInfo_ == nullptr) {
392            HILOG_ERROR("optionsInfo_ is nullptr");
393            return;
394        }
395        if (isWait_ && !(waitpid(optionsInfo_->pid, &status, WNOHANG)) && isNeedRun_) {
396            if (!kill(optionsInfo_->pid, SIGKILL)) {
397                waitpid(optionsInfo_->pid, &status, 0);
398                isWait_ = false;
399                exitCode_ = status;
400                isNeedRun_ = false;
401            } else {
402                HILOG_ERROR("close kill SIGKILL signal failed");
403            }
404        }
405    }
406
407    void ChildProcess::TimeoutListener(napi_env env, void* data)
408    {
409        std::vector<int32_t> signalType = {SIGINT, SIGQUIT, SIGKILL, SIGTERM};
410        auto temp = reinterpret_cast<OptionsInfo*>(data);
411        int32_t timeout = temp->timeout * TIME_EXCHANGE;
412        if (timeout > 0) {
413            usleep(static_cast<size_t>(timeout));
414            if (*(temp->isNeedRun)) {
415                if (!kill(temp->pid, temp->killSignal)) {
416                    auto res = std::find(signalType.begin(), signalType.end(), temp->killSignal);
417                    (res != signalType.end()) ? *(temp->isNeedRun) = false : 0;
418                } else {
419                    HILOG_ERROR("timeout kill signal failed");
420                }
421            }
422        }
423    }
424
425    void ChildProcess::InitOptionsInfo(napi_env env, napi_value options)
426    {
427        std::vector<std::string> keyStr = {"timeout", "killSignal", "maxBuffer"};
428        optionsInfo_ = new OptionsInfo();
429        if (optionsInfo_ == nullptr) {
430            HILOG_ERROR("optionsInfo_ is nullptr");
431            return;
432        }
433        size_t size = keyStr.size();
434        for (size_t i = 0; i < size; i++) {
435            napi_status status = napi_ok;
436            napi_value property = nullptr;
437            napi_get_named_property(env, options, keyStr[i].c_str(), &property);
438            switch (i) {
439                case 0:
440                    status = napi_get_value_int32(env, property, &optionsInfo_->timeout);
441                    if (status != napi_ok) {
442                        optionsInfo_->timeout = 0;
443                    }
444                    break;
445                case 1:
446                    optionsInfo_->killSignal = GetValidSignal(env, property);
447                    break;
448                case 2: // 2:The parameter value
449                    status = napi_get_value_int64(env, property, &optionsInfo_->maxBuffer);
450                    if (status != napi_ok) {
451                        optionsInfo_->maxBuffer = static_cast<int64_t>(MAXSIZE) * static_cast<int64_t>(MAXSIZE);
452                    }
453                    break;
454                default:
455                    break;
456            }
457        }
458        optionsInfo_->isNeedRun = &isNeedRun_;
459    }
460
461    std::string ChildProcess::RequireStrValue(napi_env env, const napi_value strValue)
462    {
463        size_t bufferSize = 0;
464        if (napi_get_value_string_utf8(env, strValue, nullptr, 0, &bufferSize) != napi_ok) {
465            HILOG_ERROR("can not get strValue size");
466            return nullptr;
467        }
468        std::string result = "";
469        result.reserve(bufferSize + 1);
470        result.resize(bufferSize);
471        if (napi_get_value_string_utf8(env, strValue, result.data(), bufferSize + 1, &bufferSize) != napi_ok) {
472            HILOG_ERROR("can not get strValue value");
473            return nullptr;
474        }
475        return result;
476    }
477
478    ChildProcess::~ChildProcess()
479    {
480        close(stdOutFd_[0]);
481        close(stdErrFd_[0]);
482        if (isWait_) {
483            int32_t status = 0;
484            waitpid(optionsInfo_->pid, &status, 0);
485        }
486        isNeedRun_ = false;
487    }
488} // namespace OHOS::JsSysModule::Process
489