* Copyright (C) 2021 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "async_cmd.h"
#include <pthread.h>
#if !defined(_WIN32) && !defined(HDC_HOST)
#include "parameter.h"
#include "base.h"
#if defined(SURPPORT_SELINUX)
#include "selinux/selinux.h"
#endif
#endif
namespace Hdc {
#if !defined(_WIN32) && !defined(HDC_HOST)
static bool GetDevItem(const char *key, string &out)
{
bool ret = true;
char tmpStringBuf[BUF_SIZE_MEDIUM] = "";
#ifdef HARMONY_PROJECT
auto res = GetParameter(key, nullptr, tmpStringBuf, BUF_SIZE_MEDIUM);
if (res <= 0) {
WRITE_LOG(LOG_WARN, "GetDevItem false key:%s", key);
return false;
}
#else
string sFailString = Base::StringFormat("Get parameter \"%s\" fail", key);
string stringBuf = "param get " + string(key);
Base::RunPipeComand(stringBuf.c_str(), tmpStringBuf, BUF_SIZE_MEDIUM - 1, true);
if (!strcmp(sFailString.c_str(), tmpStringBuf)) {
WRITE_LOG(LOG_WARN, "GetDevItem false tmpStringBuf:%s", tmpStringBuf);
ret = false;
(void)memset_s(tmpStringBuf, sizeof(tmpStringBuf), 0, sizeof(tmpStringBuf));
}
#endif
out = tmpStringBuf;
return ret;
}
#endif
#ifndef _WIN32
static bool IsRoot()
{
#ifndef HDC_HOST
string debugMode = "";
string rootMode = "";
GetDevItem("const.debuggable", debugMode);
GetDevItem("persist.hdc.root", rootMode);
return debugMode == "1" && rootMode == "1";
#else
return false;
#endif
}
#endif
AsyncCmd::AsyncCmd()
{
}
AsyncCmd::~AsyncCmd()
{
if (Base::CloseFd(fd) != 0) {
WRITE_LOG(LOG_INFO, "AsyncCmd Release, close fd:%d", fd);
}
if (childShell != nullptr) {
delete childShell;
childShell = nullptr;
}
};
bool AsyncCmd::ReadyForRelease()
{
if (childShell != nullptr && !childShell->ReadyForRelease()) {
WRITE_LOG(LOG_WARN, "childShell not ready for release pid:%d", pid);
return false;
}
uint32_t count = refCount.load();
if (count != 0) {
WRITE_LOG(LOG_WARN, "refCount:%u not ready for release", count);
return false;
}
if (childShell != nullptr) {
delete childShell;
childShell = nullptr;
}
return true;
}
void AsyncCmd::DoRelease()
{
if (childShell != nullptr) {
childShell->StopWorkOnThread(false, nullptr);
}
int rc = 0;
if (pid > 0) {
rc = uv_kill(pid, SIGTERM);
}
WRITE_LOG(LOG_INFO, "DoRelease rc:%d pid:%d", rc, pid);
}
bool AsyncCmd::Initial(uv_loop_t *loopIn, const CmdResultCallback callback, uint32_t optionsIn)
{
#if defined _WIN32 || defined HDC_HOST
WRITE_LOG(LOG_FATAL, "Not support for win32 or host side");
return false;
#else
loop = loopIn;
resultCallback = callback;
options = optionsIn;
return true;
#endif
}
bool AsyncCmd::FinishShellProc(const void *context, const bool result, const string exitMsg)
{
AsyncCmd *thisClass = static_cast<AsyncCmd *>(const_cast<void *>(context));
WRITE_LOG(LOG_DEBUG, "FinishShellProc finish pipeRead fd:%d pid:%d count:%u",
thisClass->fd, thisClass->pid, thisClass->refCount.load());
thisClass->resultCallback(true, result, thisClass->cmdResult + exitMsg);
--thisClass->refCount;
return true;
};
bool AsyncCmd::ChildReadCallback(const void *context, uint8_t *buf, const int size)
{
AsyncCmd *thisClass = static_cast<AsyncCmd *>(const_cast<void *>(context));
if (thisClass->options & OPTION_COMMAND_ONETIME) {
string s(reinterpret_cast<char *>(buf), size);
thisClass->cmdResult += s;
return true;
}
string s(reinterpret_cast<char *>(buf), size);
return thisClass->resultCallback(false, 0, s);
};
#if !defined(_WIN32) && !defined(HDC_HOST)
static void SetSelinuxLabel(bool isRoot)
{
#if defined(SURPPORT_SELINUX)
char *con = nullptr;
if (getcon(&con) != 0) {
WRITE_LOG(LOG_WARN, "SetSelinuxLabel isRoot:%d", isRoot);
return;
}
if ((con != nullptr) && (strcmp(con, "u:r:hdcd:s0") != 0) && (strcmp(con, "u:r:updater:s0") != 0)) {
WRITE_LOG(LOG_WARN, "SetSelinuxLabel con:%s isRoot:%d", con, isRoot);
freecon(con);
return;
}
if (isRoot) {
setcon("u:r:su:s0");
} else {
setcon("u:r:sh:s0");
}
freecon(con);
#endif
}
#endif
int AsyncCmd::ThreadFork(const string &command, const string &optionPath, int &cpid)
{
#ifdef _WIN32
return ERR_NO_SUPPORT;
#else
bool isRoot = IsRoot();
constexpr uint8_t pipeRead = 0;
constexpr uint8_t pipeWrite = 1;
pid_t childPid;
int fds[2];
if (pipe(fds) != 0) {
WRITE_LOG(LOG_FATAL, "Popen pipe failed errno:%d", errno);
return ERR_GENERIC;
}
WRITE_LOG(LOG_DEBUG, "Popen pipe fds[pipeRead]:%d fds[pipeWrite]:%d, mode %d",
fds[pipeRead], fds[pipeWrite], isRoot);
if ((childPid = fork()) == -1) {
WRITE_LOG(LOG_FATAL, "Popen fork failed errno:%d", errno);
return ERR_GENERIC;
}
if (childPid == 0) {
Base::DeInitProcess();
dup2(fds[pipeRead], STDIN_FILENO);
dup2(fds[pipeWrite], STDOUT_FILENO);
dup2(fds[pipeWrite], STDERR_FILENO);
close(fds[pipeRead]);
close(fds[pipeWrite]);
setsid();
setpgid(childPid, childPid);
#if !defined(_WIN32) && !defined(HDC_HOST)
SetSelinuxLabel(isRoot);
#endif
ChildProcessExecute(command, optionPath);
} else {
Base::CloseFd(fds[pipeWrite]);
fcntl(fds[pipeRead], F_SETFD, FD_CLOEXEC);
}
cpid = childPid;
return fds[pipeRead];
#endif
}
void AsyncCmd::ChildProcessExecute(const string &command, const string &optionPath)
{
#ifndef _WIN32
string shellPath = Base::GetShellPath();
int execlRet = 0;
if (!optionPath.empty() && chdir(optionPath.c_str()) != 0) {
string cmdEcho = "echo \"[E003006] Internal error: AsyncCmd chdir failed:" + optionPath + "\"";
execlRet = execl(shellPath.c_str(), shellPath.c_str(), "-c", cmdEcho.c_str(), NULL);
} else {
execlRet = execl(shellPath.c_str(), shellPath.c_str(), "-c", command.c_str(), NULL);
}
if (execlRet < 0) {
WRITE_LOG(LOG_FATAL, "start shell failed %d: %s", execlRet, strerror(errno));
_exit(0);
}
#endif
}
bool AsyncCmd::ExecuteCommand(const string &command, string executePath)
{
string cmd = command;
cmd = Base::ShellCmdTrim(cmd);
if ((fd = ThreadFork(cmd, executePath, pid)) < 0) {
#ifdef IS_RELEASE_VERSION
WRITE_LOG(LOG_FATAL, "ExecuteCommand failed cmd:%s fd:%d", Hdc::MaskString(cmd).c_str(), fd);
return false;
}
WRITE_LOG(LOG_DEBUG, "ExecuteCommand cmd:%s fd:%d pid:%d", Hdc::MaskString(cmd).c_str(), fd, pid);
#else
WRITE_LOG(LOG_FATAL, "ExecuteCommand failed cmd:%s fd:%d", cmd.c_str(), fd);
return false;
}
WRITE_LOG(LOG_DEBUG, "ExecuteCommand cmd:%s fd:%d pid:%d", cmd.c_str(), fd, pid);
#endif
++refCount;
childShell = new(std::nothrow) HdcFileDescriptor(loop, fd, this, ChildReadCallback, FinishShellProc, false);
if (childShell == nullptr) {
WRITE_LOG(LOG_FATAL, "ExecuteCommand new childShell failed");
--refCount;
return false;
}
if (!childShell->StartWorkOnThread()) {
WRITE_LOG(LOG_FATAL, "ExecuteCommand StartWorkOnThread failed");
return false;
}
return true;
}
}