Merge branch 'dispatcher-0429' of git@gitcode.com:xialiangwei/ability_ability_runtime.git into 'master'

# Conflicts:
#   conflict interfaces/kits/c/ability_runtime/ability_runtime_common.h
Co-Authored-By:Agent
This commit is contained in:
xialiangwei
2026-05-16 20:37:22 +08:00
705 changed files with 48649 additions and 7310 deletions
+6
View File
@@ -258,4 +258,10 @@ declare_args() {
!defined(global_parts_info.distributeddatamgr_udmf)) {
ability_runtime_udmf_enable = false
}
hiviewdfx_runtime_api_metrics_enable = true
if (defined(global_parts_info) &&
!defined(global_parts_info.hiviewdfx_api_metrics)) {
hiviewdfx_runtime_api_metrics_enable = false
}
}
@@ -20,7 +20,7 @@ interface OHOS.AAFwk.IAbilityConnection;
rawdata AgentCard..OHOS.AgentRuntime.AgentCardsRawData;
interface OHOS.AgentRuntime.IAgentManager {
void GetAllAgentCards([out] AgentCardsRawData cards);
void GetAgentCardsByBundleName([in] String bundleName, [out] AgentCard[] cards);
void GetAgentCardsByBundleName([in] String bundleName, [out] AgentCardsRawData cards);
void GetAgentCardByAgentId([in] String bundleName, [in] String agentId, [out] AgentCard card);
void GetCallerAgentCardByAgentId([in] String agentId, [out] AgentCard card);
void RegisterAgentCard([in] AgentCard card);
@@ -62,7 +62,13 @@ int32_t AgentManagerClient::GetAgentCardsByBundleName(const std::string &bundleN
TAG_LOGE(AAFwkTag::SER_ROUTER, "null agentmgr");
return ERR_NULL_AGENT_MGR_PROXY;
}
return agentMgr->GetAgentCardsByBundleName(bundleName, cards);
AgentCardsRawData rawData;
auto ret = agentMgr->GetAgentCardsByBundleName(bundleName, rawData);
if (ret != ERR_OK) {
TAG_LOGE(AAFwkTag::SER_ROUTER, "get by bundle failed: %{public}d", ret);
return ret;
}
return AgentCardsRawData::ToAgentCardVec(rawData, cards);
}
int32_t AgentManagerClient::GetAgentCardByAgentId(const std::string &bundleName, const std::string &agentId,
@@ -49,6 +49,7 @@ public:
private:
OHOS::AppExecFwk::BundleMgrClient bundleMgrClient_;
mutable std::mutex cardDataMutex_;
AgentCardMgr();
~AgentCardMgr();
};
@@ -55,7 +55,7 @@ public:
int32_t GetAllAgentCards(AgentCardsRawData &cards) override;
int32_t GetAgentCardsByBundleName(const std::string &bundleName, std::vector<AgentCard> &cards) override;
int32_t GetAgentCardsByBundleName(const std::string &bundleName, AgentCardsRawData &cards) override;
int32_t GetAgentCardByAgentId(const std::string &bundleName, const std::string &agentId, AgentCard &card) override;
@@ -102,7 +102,7 @@ private:
void Init();
void RegisterBundleEventCallback();
/**
* @brief Validates caller permission and reserves one slot from the per-caller connection quota.
* @brief Validates caller permission and foreground state before classifying the agent connect request.
*/
int32_t ValidateConnectAgentRequest(const sptr<AAFwk::IAbilityConnection> &connection, int32_t &callerUid);
/**
@@ -153,6 +153,8 @@ private:
bool ReleaseCallerConnectionCountLocked(const sptr<IRemoteObject> &callerRemote);
void ReleaseTrackedConnection(const sptr<AAFwk::IAbilityConnection> &connection);
void ReleaseTrackedConnectionByRemoteLocked(const sptr<IRemoteObject> &callerRemote);
void TransferLowCodeCallerLimitLocked(const std::shared_ptr<AgentHostSession> &session,
const sptr<IRemoteObject> &callerRemote);
void HandleCallerConnectionDied(const wptr<IRemoteObject> &remote);
void HandleCallerConnectionDied(const sptr<IRemoteObject> &remote);
/**
@@ -158,6 +158,7 @@ int32_t AgentCardMgr::HandleBundleInstall(const std::string &bundleName, int32_t
}
std::vector<StoredAgentCardEntry> storedEntries;
std::lock_guard<std::mutex> lock(cardDataMutex_);
int32_t ret = AgentCardDbMgr::GetInstance().QueryData(bundleName, userId, storedEntries);
if (ret != ERR_OK && ret != ERR_NAME_NOT_FOUND) {
TAG_LOGE(AAFwkTag::SER_ROUTER, "query stored cards failed: %{public}d", ret);
@@ -198,6 +199,7 @@ int32_t AgentCardMgr::HandleBundleRemove(const std::string &bundleName, int32_t
TAG_LOGE(AAFwkTag::SER_ROUTER, "invalid bundleName");
return -1;
}
std::lock_guard<std::mutex> lock(cardDataMutex_);
return AgentCardDbMgr::GetInstance().DeleteData(bundleName, userId);
}
@@ -213,6 +215,7 @@ int32_t AgentCardMgr::GetAgentCardsByBundleName(const std::string &bundleName, s
{
int32_t userId = IPCSkeleton::GetCallingUid() / BASE_USER_RANGE;
std::vector<StoredAgentCardEntry> entries;
std::lock_guard<std::mutex> lock(cardDataMutex_);
int32_t ret = AgentCardDbMgr::GetInstance().QueryData(bundleName, userId, entries);
if (ret == ERR_OK) {
cards = ExtractCards(entries);
@@ -275,6 +278,7 @@ int32_t AgentCardMgr::RegisterAgentCard(const AgentCard &card)
}
std::vector<StoredAgentCardEntry> entries;
std::lock_guard<std::mutex> lock(cardDataMutex_);
int32_t ret = AgentCardDbMgr::GetInstance().QueryData(registerCard.appInfo->bundleName, userId, entries);
if (ret != ERR_OK && ret != ERR_NAME_NOT_FOUND) {
TAG_LOGE(AAFwkTag::SER_ROUTER, "query data failed: %{public}d", ret);
@@ -288,6 +292,10 @@ int32_t AgentCardMgr::RegisterAgentCard(const AgentCard &card)
TAG_LOGE(AAFwkTag::SER_ROUTER, "agent card already registered");
return AAFwk::ERR_AGENT_CARD_DUPLICATE_REGISTER;
}
if (entries.size() >= MAX_AGENT_CARD_SIZE) {
TAG_LOGE(AAFwkTag::SER_ROUTER, "agent card count reached max size %{public}d", MAX_AGENT_CARD_SIZE);
return AAFwk::ERR_AGENT_CARD_LIST_OUT_OF_RANGE;
}
entries.push_back({registerCard, AgentCardUpdateSource::API});
return AgentCardDbMgr::GetInstance().InsertData(registerCard.appInfo->bundleName, userId, entries);
@@ -316,6 +324,7 @@ int32_t AgentCardMgr::UpdateAgentCard(const AgentCard &card)
}
std::vector<StoredAgentCardEntry> entries;
std::lock_guard<std::mutex> lock(cardDataMutex_);
int32_t ret = AgentCardDbMgr::GetInstance().QueryData(card.appInfo->bundleName, userId, entries);
if (ret == ERR_NAME_NOT_FOUND) {
TAG_LOGE(AAFwkTag::SER_ROUTER, "bundle cards not found");
@@ -369,6 +378,7 @@ int32_t AgentCardMgr::DeleteAgentCard(const std::string &bundleName, const std::
int32_t userId = IPCSkeleton::GetCallingUid() / BASE_USER_RANGE;
std::vector<StoredAgentCardEntry> bundleCards;
std::lock_guard<std::mutex> lock(cardDataMutex_);
int32_t ret = AgentCardDbMgr::GetInstance().QueryData(bundleName, userId, bundleCards);
if (ret == ERR_NAME_NOT_FOUND) {
TAG_LOGE(AAFwkTag::SER_ROUTER, "bundle cards not found");
@@ -175,7 +175,7 @@ int32_t AgentManagerService::GetAllAgentCards(AgentCardsRawData &cards)
return AgentCardMgr::GetInstance().GetAllAgentCards(cards);
}
int32_t AgentManagerService::GetAgentCardsByBundleName(const std::string &bundleName, std::vector<AgentCard> &cards)
int32_t AgentManagerService::GetAgentCardsByBundleName(const std::string &bundleName, AgentCardsRawData &cards)
{
if (!AAFwk::PermissionVerification::GetInstance()->JudgeCallerIsAllowedToUseSystemAPI()) {
TAG_LOGE(AAFwkTag::SER_ROUTER, "caller no system-app, can not use system-api");
@@ -186,7 +186,8 @@ int32_t AgentManagerService::GetAgentCardsByBundleName(const std::string &bundle
TAG_LOGE(AAFwkTag::SER_ROUTER, "Permission verification failed");
return ERR_PERMISSION_DENIED;
}
auto ret = AgentCardMgr::GetInstance().GetAgentCardsByBundleName(bundleName, cards);
std::vector<AgentCard> cardVec;
auto ret = AgentCardMgr::GetInstance().GetAgentCardsByBundleName(bundleName, cardVec);
if (ret == ERR_NAME_NOT_FOUND) {
TAG_LOGW(AAFwkTag::SER_ROUTER, "no agent cards of bundle %{public}s", bundleName.c_str());
int32_t userId = IPCSkeleton::GetCallingUid() / BASE_USER_RANGE;
@@ -198,8 +199,12 @@ int32_t AgentManagerService::GetAgentCardsByBundleName(const std::string &bundle
TAG_LOGE(AAFwkTag::SER_ROUTER, "bundle unexist");
return AAFwk::ERR_BUNDLE_NOT_EXIST;
}
AgentCardsRawData::FromAgentCardVec({}, cards);
return ERR_OK;
}
if (ret == ERR_OK) {
AgentCardsRawData::FromAgentCardVec(cardVec, cards);
}
return ret;
}
@@ -306,7 +311,7 @@ int32_t AgentManagerService::DeleteAgentCard(const std::string &bundleName, cons
int32_t AgentManagerService::ConnectAgentExtensionAbility(const AAFwk::Want &want,
const sptr<AAFwk::IAbilityConnection> &connection)
{
// Step 1: validate caller state and shared caller-side connection quota.
// Step 1: validate caller state before classifying the agent connect request.
int32_t callerUid = 0;
auto ret = ValidateConnectAgentRequest(connection, callerUid);
if (ret != ERR_OK) {
@@ -409,15 +414,7 @@ int32_t AgentManagerService::ValidateConnectAgentRequest(const sptr<AAFwk::IAbil
return ERR_INVALID_VALUE;
}
// Reserve only against the caller-level shared connection budget.
callerUid = IPCSkeleton::GetCallingUid();
{
std::lock_guard<std::mutex> lock(connectionLock_);
if (HasReachedCallerConnectionLimitLocked(callerUid)) {
TAG_LOGE(AAFwkTag::SER_ROUTER, "Maximum agent connections reached for callerUid: %{public}d", callerUid);
return AAFwk::ERR_MAX_AGENT_CONNECTIONS_REACHED;
}
}
// Only foreground apps are allowed to initiate agent connects.
auto callerPid = IPCSkeleton::GetCallingPid();
@@ -922,6 +919,7 @@ void AgentManagerService::ReleaseTrackedConnectionByRemoteLocked(const sptr<IRem
}
auto callerUid = it->second.callerUid;
auto countTowardsCallerLimit = it->second.countTowardsCallerLimit;
if (it->second.callerRemote != nullptr && it->second.deathRecipient != nullptr) {
it->second.callerRemote->RemoveDeathRecipient(it->second.deathRecipient);
}
@@ -931,6 +929,9 @@ void AgentManagerService::ReleaseTrackedConnectionByRemoteLocked(const sptr<IRem
if (isDisconnecting) {
return;
}
if (!countTowardsCallerLimit) {
return;
}
auto countIt = callerConnectionCounts_.find(callerUid);
if (countIt == callerConnectionCounts_.end()) {
@@ -943,6 +944,35 @@ void AgentManagerService::ReleaseTrackedConnectionByRemoteLocked(const sptr<IRem
countIt->second--;
}
void AgentManagerService::TransferLowCodeCallerLimitLocked(const std::shared_ptr<AgentHostSession> &session,
const sptr<IRemoteObject> &callerRemote)
{
if (session == nullptr || callerRemote == nullptr) {
return;
}
auto currentIter = trackedConnections_.find(callerRemote);
if (currentIter == trackedConnections_.end() || !currentIter->second.countTowardsCallerLimit) {
return;
}
for (const auto &connectionEntry : session->callerConnections) {
const auto &candidateRemote = connectionEntry.first;
if (candidateRemote == nullptr || candidateRemote == callerRemote) {
continue;
}
auto candidateIter = trackedConnections_.find(candidateRemote);
if (candidateIter == trackedConnections_.end() || !candidateIter->second.isLowCode ||
candidateIter->second.hostKey < currentIter->second.hostKey ||
currentIter->second.hostKey < candidateIter->second.hostKey ||
candidateIter->second.callerUid != currentIter->second.callerUid ||
candidateIter->second.countTowardsCallerLimit) {
continue;
}
candidateIter->second.countTowardsCallerLimit = true;
currentIter->second.countTowardsCallerLimit = false;
return;
}
}
void AgentManagerService::HandleCallerConnectionDied(const sptr<IRemoteObject> &remote)
{
sptr<AAFwk::IAbilityConnection> serviceConnection = nullptr;
@@ -976,6 +1006,8 @@ void AgentManagerService::HandleCallerConnectionDied(const sptr<IRemoteObject> &
if (!session->isDisconnecting && session->agents.empty()) {
session->isDisconnecting = true;
hostConnection = session->hostConnection;
} else {
TransferLowCodeCallerLimitLocked(session, remote);
}
}
ReleaseTrackedConnectionByRemoteLocked(remote);
@@ -1071,6 +1103,7 @@ int32_t AgentManagerService::NotifyLowCodeAgentComplete(const std::string &agent
agentOwners_.erase(ownerIter);
if (!callerStillOwnsAgent && callerRemote != nullptr) {
session->callerConnections.erase(callerRemote);
TransferLowCodeCallerLimitLocked(session, callerRemote);
ReleaseTrackedConnectionByRemoteLocked(callerRemote);
}
if (!session->agents.empty() || session->isDisconnecting) {
@@ -1170,7 +1203,8 @@ int32_t AgentManagerService::PrepareLowCodeConnectPlan(const AgentHostKey &hostK
}
}
auto ret = TryRegisterConnectionLocked(connection, callingUid, session->hostConnection, &hostKey);
auto ret = TryRegisterConnectionLocked(connection, callingUid, session->hostConnection, &hostKey,
plan.needRealConnect);
if (ret != ERR_OK) {
if (plan.needRealConnect) {
agentHostSessions_.erase(hostKey);
@@ -1214,6 +1248,8 @@ void AgentManagerService::CleanupLowCodeConnectPlan(const AgentConnectPlan &plan
}
if (session->callerConnections.empty() && session->agents.empty()) {
agentHostSessions_.erase(sessionIter);
} else {
TransferLowCodeCallerLimitLocked(session, plan.callerRemote);
}
}
if (plan.registeredTrackedConnection && plan.callerRemote != nullptr) {
+13 -3
View File
@@ -51,6 +51,7 @@
"accessibility",
"access_token",
"ace_engine",
"api_metrics",
"app_domain_verify",
"app_file_service",
"appspawn",
@@ -88,6 +89,7 @@
"ipc",
"json",
"kv_store",
"libjpeg-turbo",
"libuv",
"libxml2",
"media_library",
@@ -118,9 +120,6 @@
"zlib",
"hiperf",
"hiprofiler"
],
"third_party": [
"libjpeg-turbo"
]
},
"build": {
@@ -853,6 +852,16 @@
},
"name": "//foundation/ability/ability_runtime/frameworks/ets/ani/featureAbility:featureability_ani"
},
{
"header": {
"header_base": "//foundation/ability/ability_runtime/frameworks/native/ability/native/ability_runtime/madvise",
"header_files": [
"madvise_utils.h",
"vma_utils.h"
]
},
"name": "//foundation/ability/ability_runtime/frameworks/native/ability/native/ability_runtime/madvise:ability_madvise"
},
{
"header": {
"header_base": "//foundation/ability/ability_runtime/agent_runtime_framework/interfaces/kits/native/agent_extension/connection/include/",
@@ -879,6 +888,7 @@
}
],
"test": [
"//foundation/ability/ability_runtime/cli_tool_framework/test/unittest:unittest",
"//foundation/ability/ability_runtime/test/moduletest:moduletest",
"//foundation/ability/ability_runtime/test/fuzztest:fuzztest",
"//foundation/ability/ability_runtime/test/unittest:unittest",
@@ -12,6 +12,7 @@
# limitations under the License.
import("//build/test.gni")
import("//foundation/ability/ability_runtime/ability_runtime.gni")
module_output_path = "ability_runtime/ability_runtime/cj_environment"
@@ -20,10 +21,9 @@ ohos_unittest("cj_environment_test") {
sources = [ "cj_environment_test.cpp" ]
sources += [ "cj_invoker.h" ]
deps = []
deps = [ "${ability_runtime_path}/cj_environment/frameworks/cj_environment:cj_environment" ]
external_deps = [
"ability_runtime:cj_environment",
"googletest:gmock_main",
"googletest:gtest_main",
"hilog:libhilog",
+11 -1
View File
@@ -14,12 +14,22 @@
"uid" : "aimgr",
"gid" : ["system"],
"ondemand" : true,
"cgroup" : true,
"caps" : ["KILL"],
"secon" : "u:r:aimgr:s0",
"jobs" : {
"on-start" : "services:aimgr"
},
"permission" : [
"ohos.permission.GET_BUNDLE_INFO_PRIVILEGED"
"ohos.permission.GET_BUNDLE_INFO_PRIVILEGED",
"ohos.permission.MANAGE_TOOL_TOKENID",
"ohos.permission.RUNNING_STATE_OBSERVER",
"ohos.permission.MANAGE_SKILL_PRIVILEGE",
"ohos.permission.PARENT_CONTROL_UI",
"ohos.permission.START_ABILITIES_FROM_BACKGROUND"
],
"permission_acls" : [
"ohos.permission.MANAGE_TOOL_TOKENID"
]
}
]
+2
View File
@@ -17,5 +17,7 @@ import("//foundation/ability/ability_runtime/ability_runtime.gni")
group("cli_tool_framework_packages") {
deps = [
"${cli_tool_framework_path}/frameworks/js/napi/cli_tool_manager:climanager_napi",
"${cli_tool_framework_path}/frameworks/js/napi/skill_driver:skilldriver_napi",
"${cli_tool_framework_path}/frameworks/js/napi/script_manager:scriptmanager_napi",
]
}
@@ -14,6 +14,61 @@
import("//build/ohos.gni")
import("//foundation/ability/ability_runtime/ability_runtime.gni")
ohos_source_set("cli_manager_error_utils_src") {
sanitize = {
cfi = true
cfi_cross_dso = true
debug = false
}
include_dirs = [
"include",
"${cli_tool_framework_path}/interfaces/cli_tool/include",
"${ability_runtime_services_path}/common/include",
]
sources = [ "src/cli_manager_error_utils.cpp" ]
deps = [
"${ability_runtime_innerkits_path}/ability_manager:ability_manager",
"${ability_runtime_innerkits_path}/error_utils:ability_runtime_error_util",
"${ability_runtime_napi_path}/inner/napi_common:napi_common",
"${cli_tool_framework_path}/interfaces/cli_tool:cli_tool_client",
]
external_deps = [
"hilog:libhilog",
"napi:ace_napi",
]
subsystem_name = "ability"
part_name = "ability_runtime"
}
ohos_source_set("js_cli_event_handler_manager_src") {
sanitize = {
cfi = true
cfi_cross_dso = true
debug = false
}
include_dirs = [
"include",
"${ability_runtime_services_path}/common/include",
]
sources = [ "src/js_cli_event_handler_manager.cpp" ]
external_deps = [
"c_utils:utils",
"eventhandler:libeventhandler",
"hilog:libhilog",
]
subsystem_name = "ability"
part_name = "ability_runtime"
}
ohos_shared_library("climanager_napi") {
sanitize = {
cfi = true
@@ -28,11 +83,9 @@ ohos_shared_library("climanager_napi") {
]
sources = [
"src/cli_manager_error_utils.cpp",
"src/cli_tool_manager_module.cpp",
"src/js_cli_manager.cpp",
"src/js_cli_manager_utils.cpp",
"src/js_cli_event_handler_manager.cpp",
"src/js_cli_session_event_callback.cpp",
]
@@ -41,6 +94,8 @@ ohos_shared_library("climanager_napi") {
"${ability_runtime_innerkits_path}/runtime:runtime",
"${ability_runtime_napi_path}/inner/napi_common:napi_common",
"${cli_tool_framework_path}/interfaces/cli_tool:cli_tool_client",
":cli_manager_error_utils_src",
":js_cli_event_handler_manager_src",
]
external_deps = [
@@ -52,7 +107,7 @@ ohos_shared_library("climanager_napi") {
"napi:ace_napi",
]
relative_install_dir = "module/app/ability"
relative_install_dir = "module/app/cli"
subsystem_name = "ability"
part_name = "ability_runtime"
}
@@ -19,7 +19,6 @@
#include <map>
#include <string>
#include "arg_mapping.h"
#include "native_engine/native_engine.h"
#include "tool_info.h"
#include "tool_summary.h"
@@ -29,6 +28,7 @@ namespace CliTool {
class CliSessionInfo;
class CliToolEvent;
class ExecOptions;
/**
* @brief Unwrap a string map from JavaScript object.
* @param env The N-API environment.
@@ -66,14 +66,6 @@ bool IsValidToolEventCallback(napi_env env, napi_value obj);
*/
napi_value CreateJsCliToolEvent(napi_env env, const CliToolEvent &event);
/**
* @brief Create JavaScript ArgMapping object.
* @param env The N-API environment.
* @param argMapping The ArgMapping structure.
* @return Returns the JavaScript object.
*/
napi_value CreateJsArgMapping(napi_env env, const ArgMapping &argMapping);
/**
* @brief Create JavaScript SubCommandInfo object.
* @param env The N-API environment.
@@ -20,10 +20,10 @@ static napi_module _module = {
.nm_version = 0,
.nm_filename = "app/cli_tool/climanager_napi.so/cli_manager.js",
.nm_register_func = OHOS::CliTool::JSCliManagerInit,
.nm_modname = "app.ability.cliManager",
.nm_modname = "app.cli.cliManager",
};
extern "C" __attribute__((constructor)) void NAPI_app_ability_CliManager_AutoRegister(void)
extern "C" __attribute__((constructor)) void NAPI_app_cli_CliManager_AutoRegister(void)
{
napi_module_register(&_module);
}
@@ -15,23 +15,21 @@
#include "js_cli_manager.h"
#include <map>
#include <string>
#include "cli_error_code.h"
#include "cli_manager_error_utils.h"
#include "cli_session_info.h"
#include "cli_tool_mgr_client.h"
#include "exec_tool_callback_impl.h"
#include "exec_result.h"
#include "hilog_tag_wrapper.h"
#include "js_cli_event_handler_manager.h"
#include "js_cli_manager_utils.h"
#include "js_cli_session_event_callback.h"
#include "js_error_utils.h"
#include "napi_common_util.h"
#include "napi_common_want.h"
#include "js_cli_event_handler_manager.h"
#include "js_cli_session_event_callback.h"
using namespace OHOS::AbilityRuntime;
namespace OHOS {
@@ -42,6 +40,29 @@ constexpr int32_t INDEX_ONE = 1;
constexpr int32_t INDEX_TWO = 2;
constexpr int32_t INDEX_THREE = 3;
constexpr int32_t INDEX_FOUR = 4;
int32_t DispatchCliTool(const ExecToolParam &param, napi_env env,
std::shared_ptr<NapiAsyncTask> asyncTask)
{
CliToolMGRClient::ExecToolReplyCallback replyCallback =
[env, asyncTask](int32_t resultCode, const CliSessionInfo &session) {
JsCliEventHandlerManager::GetInstance().PostTask(
[env, asyncTask, resultCode, session]() {
HandleScope handleScope(env);
if (resultCode != ERR_OK) {
asyncTask->Reject(env, CreateCliJsErrorByNativeErr(env, resultCode));
return;
}
napi_value jsSession = CreateJsCliSessionInfo(env, session);
if (jsSession == nullptr) {
asyncTask->Reject(env, CreateJsUndefined(env));
return;
}
asyncTask->ResolveWithNoError(env, jsSession);
});
};
return CliToolMGRClient::GetInstance().ExecTool(param, replyCallback);
}
} // namespace
void JSCliManager::Finalizer(napi_env env, void *data, void *hint)
@@ -115,7 +136,7 @@ napi_value JSCliManager::OnExecTool(napi_env env, size_t argc, napi_value *argv)
return CreateJsUndefined(env);
}
if (!AppExecFwk::UnwrapStringFromJS2(env, argv[INDEX_THREE], param.challenge)) {
if (!AppExecFwk::UnwrapStringFromJS2(env, argv[INDEX_THREE], param.challenge) || param.challenge.empty()) {
ThrowInvalidParamError(env, "Tool challenge is required");
return CreateJsUndefined(env);
}
@@ -131,24 +152,7 @@ napi_value JSCliManager::OnExecTool(napi_env env, size_t argc, napi_value *argv)
auto uasyncTask = CreateAsyncTaskWithLastParam(env, nullptr, nullptr, nullptr, &result);
std::shared_ptr<NapiAsyncTask> asyncTask = std::move(uasyncTask);
// Create callback task that will be invoked when ExecTool completes
CliToolMGRClient::ExecToolReplyCallback replyCallback =
[env, asyncTask](int32_t resultCode, const CliSessionInfo &session) {
JsCliEventHandlerManager::GetInstance().PostTask([env, asyncTask, resultCode, session]() {
HandleScope handleScope(env);
if (resultCode != ERR_OK) {
asyncTask->Reject(env, CreateCliJsErrorByNativeErr(env, resultCode));
return;
}
napi_value jsSession = CreateJsCliSessionInfo(env, session);
if (jsSession == nullptr) {
asyncTask->Reject(env, CreateJsUndefined(env));
return;
}
asyncTask->ResolveWithNoError(env, jsSession);
});
};
int32_t errCode = CliToolMGRClient::GetInstance().ExecTool(param, replyCallback);
int32_t errCode = DispatchCliTool(param, env, asyncTask);
if (errCode != ERR_OK) {
asyncTask->Reject(env, CreateCliJsErrorByNativeErr(env, errCode));
}
@@ -456,7 +460,7 @@ napi_value JSCliManagerInit(napi_env env, napi_value exportObj)
BindNativeFunction(env, exportObj, "subscribeSession", moduleName, JSCliManager::SubscribeSession);
BindNativeFunction(env, exportObj, "clearSession", moduleName, JSCliManager::ClearSession);
BindNativeFunction(env, exportObj, "querySession", moduleName, JSCliManager::QuerySession);
BindNativeFunction(env, exportObj, "sendMsg", moduleName, JSCliManager::SendMessage);
BindNativeFunction(env, exportObj, "sendMessage", moduleName, JSCliManager::SendMessage);
BindNativeFunction(env, exportObj, "getToolInfoByName", moduleName, JSCliManager::GetToolInfoByName);
BindNativeFunction(env, exportObj, "queryToolSummaries", moduleName, JSCliManager::QueryToolSummaries);
BindNativeFunction(env, exportObj, "queryTools", moduleName, JSCliManager::QueryTools);
@@ -21,6 +21,7 @@
#include "cli_tool_event.h"
#include "exec_options.h"
#include "hilog_tag_wrapper.h"
#include "icli_tool_data.h"
#include "napi_common_util.h"
using namespace OHOS::AbilityRuntime;
@@ -28,29 +29,6 @@ using namespace OHOS::AbilityRuntime;
namespace OHOS {
namespace CliTool {
namespace {
const std::string ARG_MAPPING_TYPE_FLAG = "flag";
const std::string ARG_MAPPING_TYPE_POSITIONAL = "positional";
const std::string ARG_MAPPING_TYPE_FLATTENED = "flattened";
const std::string ARG_MAPPING_TYPE_JSONSTRING = "jsonString";
const std::string ARG_MAPPING_TYPE_MIXED = "mixed";
std::string ArgMappingTypeToString(ArgMappingType type)
{
switch (type) {
case ArgMappingType::FLAG:
return ARG_MAPPING_TYPE_FLAG;
case ArgMappingType::POSITIONAL:
return ARG_MAPPING_TYPE_POSITIONAL;
case ArgMappingType::FLATTENED:
return ARG_MAPPING_TYPE_FLATTENED;
case ArgMappingType::JSONSTRING:
return ARG_MAPPING_TYPE_JSONSTRING;
case ArgMappingType::MIXED:
return ARG_MAPPING_TYPE_MIXED;
default:
return ARG_MAPPING_TYPE_FLAG;
}
}
napi_value ParseJsonStringToJsObject(napi_env env, const std::string &jsonStr)
{
@@ -207,7 +185,7 @@ bool UnwrapExecOptions(napi_env env, napi_value obj, ExecOptions &options)
TAG_LOGE(AAFwkTag::CLI_TOOL, "invalid yieldMs property");
return false;
}
if (!AppExecFwk::UnwrapInt32FromJS2(env, yieldMsProp, options.yieldMs)) {
if (!AppExecFwk::UnwrapInt64FromJS2(env, yieldMsProp, options.yieldMs)) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "unwrap yieldMs failed");
return false;
}
@@ -223,7 +201,7 @@ bool UnwrapExecOptions(napi_env env, napi_value obj, ExecOptions &options)
TAG_LOGE(AAFwkTag::CLI_TOOL, "invalid timeout property");
return false;
}
if (!AppExecFwk::UnwrapInt32FromJS2(env, timeoutProp, options.timeout)) {
if (!AppExecFwk::UnwrapInt64FromJS2(env, timeoutProp, options.timeout)) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "unwrap timeout failed");
return false;
}
@@ -246,22 +224,29 @@ napi_value CreateJsCliSessionInfo(napi_env env, const CliSessionInfo &session)
napi_set_named_property(env, jsObj, "status", AppExecFwk::WrapStringToJS(env, session.status));
// Set result if present
if (session.result != nullptr) {
if (session.status != "running" && session.result != nullptr) {
napi_value jsResult = nullptr;
status = napi_create_object(env, &jsResult);
if (status != napi_ok) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Failed to create JS ExecResult");
return nullptr;
}
napi_set_named_property(env, jsResult, "exitCode", AppExecFwk::WrapInt32ToJS(env, session.result->exitCode));
// Set outputText
napi_value jsOutputText = AppExecFwk::WrapStringToJS(env, session.result->outputText);
napi_set_named_property(env, jsResult, "outputText", jsOutputText);
// Set errorText
napi_set_named_property(env, jsResult, "errorText", AppExecFwk::WrapStringToJS(env, session.result->errorText));
// Set signalNumber
napi_value jsSignalNumber = AppExecFwk::WrapInt32ToJS(env, session.result->signalNumber);
napi_set_named_property(env, jsResult, "signalNumber", jsSignalNumber);
if (!session.result->timedOut) {
napi_value jsExitCode = AppExecFwk::WrapInt32ToJS(env, session.result->exitCode);
napi_set_named_property(env, jsResult, "exitCode", jsExitCode);
}
if (!session.result->outputText.empty()) {
napi_value jsOutputText = AppExecFwk::WrapStringToJS(env, session.result->outputText);
napi_set_named_property(env, jsResult, "outputText", jsOutputText);
}
if (!session.result->errorText.empty()) {
napi_value jsErrorText = AppExecFwk::WrapStringToJS(env, session.result->errorText);
napi_set_named_property(env, jsResult, "errorText", jsErrorText);
}
if (session.result->signalNumber != 0) {
napi_value jsSignalNumber = AppExecFwk::WrapInt32ToJS(env, session.result->signalNumber);
napi_set_named_property(env, jsResult, "signalNumber", jsSignalNumber);
}
// Set timedOut
napi_set_named_property(env, jsResult, "timedOut", AppExecFwk::WrapBoolToJS(env, session.result->timedOut));
// Set executionTime
@@ -273,34 +258,6 @@ napi_value CreateJsCliSessionInfo(napi_env env, const CliSessionInfo &session)
return handleEscape.Escape(jsObj);
}
napi_value CreateJsArgMapping(napi_env env, const ArgMapping &argMapping)
{
napi_value jsObj = nullptr;
napi_status status = napi_create_object(env, &jsObj);
if (status != napi_ok) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Failed to create JS object");
return nullptr;
}
// Set type (string: 'flag', 'positional', 'flattened', 'jsonString', 'mixed')
napi_value jsType = AppExecFwk::WrapStringToJS(env, ArgMappingTypeToString(argMapping.type));
napi_set_named_property(env, jsObj, "type", jsType);
// Set separator
napi_value jsSeparator = AppExecFwk::WrapStringToJS(env, argMapping.separator);
napi_set_named_property(env, jsObj, "separator", jsSeparator);
// Set order
napi_value jsOrder = AppExecFwk::WrapStringToJS(env, argMapping.order);
napi_set_named_property(env, jsObj, "order", jsOrder);
// Set templates (parse JSON string to object)
napi_value jsTemplates = ParseJsonStringToJsObject(env, argMapping.templates);
napi_set_named_property(env, jsObj, "templates", jsTemplates);
return jsObj;
}
napi_value CreateJsSubCommandInfo(napi_env env, const SubCommandInfo &subcmd)
{
napi_value jsObj = nullptr;
@@ -331,14 +288,6 @@ napi_value CreateJsSubCommandInfo(napi_env env, const SubCommandInfo &subcmd)
napi_value jsOutputSchema = ParseJsonStringToJsObject(env, subcmd.outputSchema);
napi_set_named_property(env, jsObj, "outputSchema", jsOutputSchema);
// Set argMapping
if (subcmd.argMapping != nullptr) {
napi_value jsArgMapping = CreateJsArgMapping(env, *subcmd.argMapping);
if (jsArgMapping != nullptr) {
napi_set_named_property(env, jsObj, "argMapping", jsArgMapping);
}
}
// Set eventTypes (array)
napi_value jsEventTypes = nullptr;
napi_create_array(env, &jsEventTypes);
@@ -440,14 +389,6 @@ napi_value CreateJsToolInfo(napi_env env, const ToolInfo &tool)
napi_value jsOutputSchema = ParseJsonStringToJsObject(env, tool.outputSchema);
napi_set_named_property(env, jsObj, "outputSchema", jsOutputSchema);
// Set argMapping
if (tool.argMapping != nullptr) {
napi_value jsArgMapping = CreateJsArgMapping(env, *tool.argMapping);
if (jsArgMapping != nullptr) {
napi_set_named_property(env, jsObj, "argMapping", jsArgMapping);
}
}
// Set eventTypes (array)
napi_value jsEventTypes = nullptr;
napi_create_array(env, &jsEventTypes);
@@ -461,10 +402,6 @@ napi_value CreateJsToolInfo(napi_env env, const ToolInfo &tool)
napi_value jsEventSchemas = ParseJsonStringToJsObject(env, tool.eventSchemas);
napi_set_named_property(env, jsObj, "eventSchemas", jsEventSchemas);
// Set timeout
napi_value jsTimeout = AppExecFwk::WrapInt32ToJS(env, tool.timeout);
napi_set_named_property(env, jsObj, "timeout", jsTimeout);
// Set hasSubCommand
napi_value jsHasSubCommand = AppExecFwk::WrapBoolToJS(env, tool.hasSubCommand);
napi_set_named_property(env, jsObj, "hasSubCommand", jsHasSubCommand);
@@ -0,0 +1,54 @@
# Copyright (c) 2026 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.
import("//build/ohos.gni")
import("//foundation/ability/ability_runtime/ability_runtime.gni")
ohos_shared_library("scriptmanager_napi") {
sanitize = {
cfi = true
cfi_cross_dso = true
debug = false
}
include_dirs = [
"include",
"${ability_runtime_path}/interfaces/kits/native/ability/ability_runtime",
"${ability_runtime_services_path}/common/include",
]
sources = [
"src/js_script_manager.cpp",
"src/script_manager_module.cpp",
]
deps = [
"${ability_runtime_innerkits_path}/ability_manager:ability_manager",
"${ability_runtime_innerkits_path}/napi_base_context:napi_base_context",
"${ability_runtime_innerkits_path}/runtime:runtime",
"${ability_runtime_napi_path}/inner/napi_common:napi_common",
]
external_deps = [
"ability_base:base",
"ability_base:want",
"c_utils:utils",
"hilog:libhilog",
"ipc:ipc_single",
"napi:ace_napi",
]
relative_install_dir = "module/app/ability"
subsystem_name = "ability"
part_name = "ability_runtime"
}
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2026 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.
*/
#ifndef OHOS_ABILITY_RUNTIME_JS_SCRIPT_MANAGER_H
#define OHOS_ABILITY_RUNTIME_JS_SCRIPT_MANAGER_H
#include "native_engine/native_engine.h"
namespace OHOS {
namespace AbilityRuntime {
class JSScriptManager final {
public:
JSScriptManager() {}
~JSScriptManager() {}
static void Finalizer(napi_env env, void *data, void *hint);
static napi_value CompleteArkTSScriptInApp(napi_env env, napi_callback_info info);
static napi_value CompleteArkTSScript(napi_env env, napi_callback_info info);
private:
napi_value OnCompleteArkTSScriptInApp(napi_env env, size_t argc, napi_value *argv);
napi_value OnCompleteArkTSScript(napi_env env, size_t argc, napi_value *argv);
};
napi_value JSScriptManagerInit(napi_env env, napi_value exportObj);
} // namespace AbilityRuntime
} // namespace OHOS
#endif // OHOS_ABILITY_RUNTIME_JS_SCRIPT_MANAGER_H
@@ -0,0 +1,193 @@
/*
* Copyright (c) 2026 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 "js_script_manager.h"
#include <string>
#include "ability_manager_client.h"
#include "hilog_tag_wrapper.h"
#include "js_error_utils.h"
#include "napi_common_skill_execute.h"
#include "napi_common_util.h"
#include "napi_base_context.h"
namespace OHOS {
namespace AbilityRuntime {
namespace {
constexpr int32_t INDEX_ZERO = 0;
constexpr int32_t INDEX_ONE = 1;
constexpr int32_t INDEX_TWO = 2;
constexpr int32_t INDEX_THREE = 3;
constexpr int32_t ERR_CONTEXT_NOT_ABILITY = 16000020;
bool HasPropertyOfType(napi_env env, napi_value obj, const char *prop)
{
napi_value value = nullptr;
napi_get_named_property(env, obj, prop, &value);
napi_valuetype type = napi_undefined;
napi_typeof(env, value, &type);
return type == napi_object;
}
bool VerifyContext(napi_env env, napi_value value)
{
if (value == nullptr) {
return false;
}
napi_valuetype valueType = napi_undefined;
napi_typeof(env, value, &valueType);
if (valueType != napi_object) {
return false;
}
return HasPropertyOfType(env, value, "abilityInfo") ||
HasPropertyOfType(env, value, "extensionAbilityInfo");
}
void ThrowContextNotValidError(napi_env env)
{
ThrowError(env, ERR_CONTEXT_NOT_ABILITY,
"The context is not a valid ability or extension context.");
}
std::string ParseRequestCode(napi_env env, napi_value value)
{
napi_valuetype type = napi_undefined;
napi_typeof(env, value, &type);
if (type == napi_string) {
size_t len = 0;
napi_get_value_string_utf8(env, value, nullptr, 0, &len);
std::string result(len, '\0');
napi_get_value_string_utf8(env, value, result.data(), len + 1, &len);
return result;
}
if (type == napi_number) {
double val = 0;
napi_get_value_double(env, value, &val);
return std::to_string(static_cast<int64_t>(val));
}
if (type == napi_bigint) {
bool lossless = true;
int64_t requestCode = 0;
napi_get_value_bigint_int64(env, value, &requestCode, &lossless);
return std::to_string(requestCode);
}
return "";
}
} // namespace
void JSScriptManager::Finalizer(napi_env env, void *data, void *hint)
{
std::unique_ptr<JSScriptManager>(static_cast<JSScriptManager *>(data));
}
napi_value JSScriptManager::CompleteArkTSScriptInApp(napi_env env, napi_callback_info info)
{
GET_CB_INFO_AND_CALL(env, info, JSScriptManager, OnCompleteArkTSScriptInApp);
}
napi_value JSScriptManager::CompleteArkTSScript(napi_env env, napi_callback_info info)
{
GET_CB_INFO_AND_CALL(env, info, JSScriptManager, OnCompleteArkTSScript);
}
napi_value JSScriptManager::OnCompleteArkTSScriptInApp(napi_env env, size_t argc, napi_value *argv)
{
TAG_LOGD(AAFwkTag::JSNAPI, "JSScriptManager::OnCompleteArkTSScriptInApp called");
HandleEscape handleEscape(env);
if (argc < INDEX_THREE) {
ThrowTooFewParametersError(env);
return CreateJsUndefined(env);
}
if (!VerifyContext(env, argv[INDEX_ZERO])) {
ThrowContextNotValidError(env);
return CreateJsUndefined(env);
}
auto context = GetStageModeContext(env, argv[INDEX_ZERO]);
sptr<IRemoteObject> token = (context != nullptr) ? context->GetToken() : nullptr;
if (token == nullptr) {
ThrowInvalidParamError(env, "failed to get token from context");
return CreateJsUndefined(env);
}
std::string requestCode = ParseRequestCode(env, argv[INDEX_ONE]);
if (requestCode.empty()) {
ThrowInvalidParamError(env, "requestCode must be a non-empty string");
return CreateJsUndefined(env);
}
AppExecFwk::SkillExecuteResult skillResult;
if (!UnwrapSkillExecuteResult(env, argv[INDEX_TWO], skillResult)) {
ThrowInvalidParamError(env, "result must be a valid ExecuteResult");
return CreateJsUndefined(env);
}
TAG_LOGD(AAFwkTag::JSNAPI,
"completeArkTSScriptInApp reqCode:%{public}s code:%{public}d",
requestCode.c_str(), skillResult.code);
auto innerErrCode = std::make_shared<int32_t>(ERR_OK);
NapiAsyncTask::ExecuteCallback execute =
[innerErrCode, token, requestCode, skillResult]() {
*innerErrCode = AAFwk::AbilityManagerClient::GetInstance()->ExecuteSkillDone(
token, requestCode, skillResult.code, skillResult);
};
NapiAsyncTask::CompleteCallback complete =
[innerErrCode](napi_env env, NapiAsyncTask &task, int32_t status) {
HandleScope handleScope(env);
if (*innerErrCode != ERR_OK) {
TAG_LOGE(AAFwkTag::JSNAPI,
"completeArkTSScriptInApp error: %{public}d", *innerErrCode);
task.Reject(env, CreateJsErrorByNativeErr(env, *innerErrCode));
return;
}
task.ResolveWithNoError(env, CreateJsUndefined(env));
};
napi_value asyncResult = nullptr;
NapiAsyncTask::Schedule("JSScriptManager::OnCompleteArkTSScriptInApp", env,
CreateAsyncTaskWithLastParam(env, nullptr, std::move(execute),
std::move(complete), &asyncResult));
return handleEscape.Escape(asyncResult);
}
napi_value JSScriptManager::OnCompleteArkTSScript(napi_env env, size_t argc, napi_value *argv)
{
TAG_LOGW(AAFwkTag::JSNAPI,
"completeArkTSScript is not supported for independent skill yet");
ThrowError(env, 401, "completeArkTSScript is not supported yet");
return CreateJsUndefined(env);
}
napi_value JSScriptManagerInit(napi_env env, napi_value exportObj)
{
TAG_LOGD(AAFwkTag::JSNAPI, "Init JSScriptManager");
if (env == nullptr || exportObj == nullptr) {
TAG_LOGW(AAFwkTag::JSNAPI, "Null env or exportObj");
return nullptr;
}
std::unique_ptr<JSScriptManager> jsScriptManager = std::make_unique<JSScriptManager>();
napi_wrap(env, exportObj, jsScriptManager.release(),
JSScriptManager::Finalizer, nullptr, nullptr);
const char *moduleName = "ScriptManager";
BindNativeFunction(env, exportObj, "completeArkTSScriptInApp", moduleName,
JSScriptManager::CompleteArkTSScriptInApp);
BindNativeFunction(env, exportObj, "completeArkTSScript", moduleName,
JSScriptManager::CompleteArkTSScript);
TAG_LOGD(AAFwkTag::JSNAPI, "JSScriptManagerInit end");
return CreateJsUndefined(env);
}
} // namespace AbilityRuntime
} // namespace OHOS
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2026 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 "native_engine/native_engine.h"
#include "js_script_manager.h"
static napi_module _module = {
.nm_version = 0,
.nm_filename = "app/ability/scriptmanager_napi.so/script_manager.js",
.nm_register_func = OHOS::AbilityRuntime::JSScriptManagerInit,
.nm_modname = "app.ability.scriptManager",
};
extern "C" __attribute__((constructor)) void NAPI_application_ScriptManager_AutoRegister(void)
{
napi_module_register(&_module);
}
@@ -0,0 +1,58 @@
# Copyright (c) 2026 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.
import("//build/ohos.gni")
import("//foundation/ability/ability_runtime/ability_runtime.gni")
ohos_shared_library("skilldriver_napi") {
sanitize = {
cfi = true
cfi_cross_dso = true
debug = false
}
include_dirs = [
"include",
"${cli_tool_framework_path}/frameworks/js/napi/cli_tool_manager/include",
"${cli_tool_framework_path}/interfaces/cli_tool/include",
"${ability_runtime_path}/interfaces/kits/native/ability/ability_runtime",
"${ability_runtime_services_path}/common/include",
]
sources = [
"src/js_skill_driver.cpp",
"src/skill_driver_module.cpp",
]
deps = [
"${ability_runtime_innerkits_path}/ability_manager:ability_manager",
"${ability_runtime_innerkits_path}/runtime:runtime",
"${ability_runtime_napi_path}/inner/napi_common:napi_common",
"//foundation/ability/ability_runtime/cli_tool_framework/frameworks/js/napi/cli_tool_manager:cli_manager_error_utils_src",
"//foundation/ability/ability_runtime/cli_tool_framework/frameworks/js/napi/cli_tool_manager:js_cli_event_handler_manager_src",
]
external_deps = [
"ability_base:base",
"ability_base:want",
"c_utils:utils",
"eventhandler:libeventhandler",
"hilog:libhilog",
"ipc:ipc_single",
"napi:ace_napi",
]
relative_install_dir = "module/app/ability"
subsystem_name = "ability"
part_name = "ability_runtime"
}
@@ -0,0 +1,41 @@
/*
* Copyright (c) 2026 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.
*/
#ifndef OHOS_ABILITY_RUNTIME_JS_SKILL_DRIVER_H
#define OHOS_ABILITY_RUNTIME_JS_SKILL_DRIVER_H
#include "native_engine/native_engine.h"
namespace OHOS {
namespace CliTool {
class JSSkillDriver final {
public:
JSSkillDriver() {}
~JSSkillDriver() {}
static void Finalizer(napi_env env, void *data, void *hint);
static napi_value ExecSkillTool(napi_env env, napi_callback_info info);
private:
napi_value OnExecSkillTool(napi_env env, size_t argc, napi_value *argv);
};
napi_value JSSkillDriverInit(napi_env env, napi_value exportObj);
} // namespace CliTool
} // namespace OHOS
#endif // OHOS_ABILITY_RUNTIME_JS_SKILL_DRIVER_H
@@ -0,0 +1,378 @@
/*
* Copyright (c) 2026 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 "js_skill_driver.h"
#include <set>
#include <string>
#include "ability_manager_client.h"
#include "array_wrapper.h"
#include "cli_error_code.h"
#include "bool_wrapper.h"
#include "cli_manager_error_utils.h"
#include "double_wrapper.h"
#include "hilog_tag_wrapper.h"
#include "int_wrapper.h"
#include "js_cli_event_handler_manager.h"
#include "js_error_utils.h"
#include "js_runtime_utils.h"
#include "long_wrapper.h"
#include "napi_common_skill_execute.h"
#include "napi_common_util.h"
#include "napi_common_want.h"
#include "skill/skill_execute_callback_stub.h"
#include "string_wrapper.h"
#include "want_params.h"
using namespace OHOS::AbilityRuntime;
namespace OHOS {
namespace CliTool {
namespace {
constexpr int32_t INDEX_ZERO = 0;
constexpr int32_t INDEX_TWO = 2;
std::string GetStringPropertyFromJs(napi_env env, napi_value obj, const std::string &key)
{
napi_value value = nullptr;
napi_get_named_property(env, obj, key.c_str(), &value);
if (value == nullptr) {
return "";
}
std::string result;
if (!AppExecFwk::UnwrapStringFromJS2(env, value, result)) {
return "";
}
return result;
}
std::string GetPropertyKeyFromJs(napi_env env, napi_value keyVal)
{
size_t strLen = 0;
napi_get_value_string_utf8(env, keyVal, nullptr, 0, &strLen);
std::string key(strLen, '\0');
napi_get_value_string_utf8(env, keyVal, key.data(), strLen + 1, &strLen);
return key;
}
void SetSkillArrayString(const std::string &key, const std::vector<std::string> &values,
AAFwk::WantParams &params)
{
auto arr = sptr<AAFwk::IArray>(new (std::nothrow) AAFwk::Array(values.size(), AAFwk::g_IID_IString));
if (arr == nullptr) { return; }
for (size_t i = 0; i < values.size(); i++) {
arr->Set(i, AAFwk::String::Box(values[i]));
}
params.SetParam(key, arr);
}
void SetSkillArrayBool(const std::string &key, const std::vector<bool> &values,
AAFwk::WantParams &params)
{
auto arr = sptr<AAFwk::IArray>(new (std::nothrow) AAFwk::Array(values.size(), AAFwk::g_IID_IBoolean));
if (arr == nullptr) { return; }
for (size_t i = 0; i < values.size(); i++) {
arr->Set(i, AAFwk::Boolean::Box(values[i]));
}
params.SetParam(key, arr);
}
void SetSkillArrayDouble(const std::string &key, const std::vector<double> &values,
AAFwk::WantParams &params)
{
auto arr = sptr<AAFwk::IArray>(new (std::nothrow) AAFwk::Array(values.size(), AAFwk::g_IID_IDouble));
if (arr == nullptr) { return; }
for (size_t i = 0; i < values.size(); i++) {
arr->Set(i, AAFwk::Double::Box(values[i]));
}
params.SetParam(key, arr);
}
void SetSkillArrayLong(const std::string &key, const std::vector<int64_t> &values,
AAFwk::WantParams &params)
{
auto arr = sptr<AAFwk::IArray>(new (std::nothrow) AAFwk::Array(values.size(), AAFwk::g_IID_ILong));
if (arr == nullptr) { return; }
for (size_t i = 0; i < values.size(); i++) {
arr->Set(i, AAFwk::Long::Box(values[i]));
}
params.SetParam(key, arr);
}
void SetSkillArrayParam(napi_env env, const std::string &key, napi_value val,
AAFwk::WantParams &params)
{
uint32_t size = 0;
if (!AppExecFwk::IsArrayForNapiValue(env, val, size) || size == 0) {
return;
}
napi_value elem = nullptr;
napi_get_element(env, val, 0, &elem);
if (elem == nullptr) { return; }
napi_valuetype elemType = napi_undefined;
napi_typeof(env, elem, &elemType);
switch (elemType) {
case napi_string: {
std::vector<std::string> values;
if (AppExecFwk::UnwrapArrayStringFromJS(env, val, values)) {
SetSkillArrayString(key, values, params);
}
break;
}
case napi_number: {
std::vector<double> dblValues;
if (AppExecFwk::UnwrapArrayDoubleFromJS(env, val, dblValues)) {
SetSkillArrayDouble(key, dblValues, params);
}
break;
}
case napi_boolean: {
std::vector<bool> values;
if (AppExecFwk::UnwrapArrayBoolFromJS(env, val, values)) {
SetSkillArrayBool(key, values, params);
}
break;
}
case napi_bigint: {
std::vector<int64_t> values;
if (AppExecFwk::UnwrapArrayInt64FromJS(env, val, values)) {
SetSkillArrayLong(key, values, params);
}
break;
}
default:
break;
}
}
void SetSkillParamByType(napi_env env, const std::string &key, napi_value val, AAFwk::WantParams &params)
{
napi_valuetype type = napi_undefined;
napi_typeof(env, val, &type);
switch (type) {
case napi_string: {
std::string str;
if (AppExecFwk::UnwrapStringFromJS2(env, val, str)) {
params.SetParam(key, AAFwk::String::Box(str));
}
break;
}
case napi_number: {
double dblVal = 0.0;
napi_get_value_double(env, val, &dblVal);
int32_t intVal = static_cast<int32_t>(dblVal);
if (static_cast<double>(intVal) == dblVal) {
params.SetParam(key, AAFwk::Integer::Box(intVal));
} else {
params.SetParam(key, AAFwk::Double::Box(dblVal));
}
break;
}
case napi_boolean: {
bool boolVal = false;
napi_get_value_bool(env, val, &boolVal);
params.SetParam(key, AAFwk::Boolean::Box(boolVal));
break;
}
case napi_bigint: {
int64_t int64Val = 0;
bool lossless = true;
napi_get_value_bigint_int64(env, val, &int64Val, &lossless);
params.SetParam(key, AAFwk::Long::Box(int64Val));
break;
}
case napi_object: {
SetSkillArrayParam(env, key, val, params);
break;
}
default:
break;
}
}
std::shared_ptr<AAFwk::WantParams> ExtractSkillArgs(napi_env env, napi_value obj)
{
auto skillArgs = std::make_shared<AAFwk::WantParams>();
napi_value propertyNames = nullptr;
napi_get_property_names(env, obj, &propertyNames);
if (propertyNames == nullptr) {
return skillArgs;
}
const std::set<std::string> reservedKeys = {
"skillToolType", "bundleName", "moduleName", "skillName", "arkTSPath", "funcName"
};
uint32_t length = 0;
napi_get_array_length(env, propertyNames, &length);
for (uint32_t i = 0; i < length; i++) {
napi_value keyVal = nullptr;
napi_get_element(env, propertyNames, i, &keyVal);
if (keyVal == nullptr) { continue; }
std::string key = GetPropertyKeyFromJs(env, keyVal);
if (key.empty() || reservedKeys.count(key) > 0) { continue; }
napi_value val = nullptr;
napi_get_named_property(env, obj, key.c_str(), &val);
if (val == nullptr) { continue; }
SetSkillParamByType(env, key, val, *skillArgs);
}
return skillArgs;
}
class SkillExecuteCallbackImpl : public AAFwk::SkillExecuteCallbackStub {
public:
explicit SkillExecuteCallbackImpl(napi_env env, napi_deferred deferred)
: env_(env), deferred_(deferred) {}
void OnExecuteDone(const std::string &requestCode, int32_t resultCode,
const AppExecFwk::SkillExecuteResult &result) override
{
TAG_LOGD(AAFwkTag::CLI_TOOL,
"SkillExecuteCallbackImpl::OnExecuteDone req:%{public}s code:%{public}d",
requestCode.c_str(), resultCode);
auto resultCopy = result;
auto deferred = deferred_;
JsCliEventHandlerManager::GetInstance().PostTask(
[env = env_, deferred, resultCopy]() {
HandleScope handleScope(env);
napi_value jsResult = WrapSkillExecuteResult(env, resultCopy);
napi_resolve_deferred(env, deferred, jsResult);
});
}
private:
napi_env env_ = nullptr;
napi_deferred deferred_ = nullptr;
};
int32_t DispatchExecuteSkill(const std::string &bundleName, const std::string &moduleName,
const std::string &skillName, const std::string &arkTSPath, const std::string &funcName,
const std::shared_ptr<AAFwk::WantParams> &skillArgs,
const sptr<SkillExecuteCallbackImpl> &callback)
{
constexpr int32_t SKILL_TYPE_INDEPENDENT = -1;
int32_t skillType = 0;
auto queryRet = AAFwk::AbilityManagerClient::GetInstance()->QuerySkillType(
bundleName, moduleName, skillName, skillType);
if (queryRet != ERR_OK) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "querySkillType failed:%{public}d", queryRet);
return queryRet;
}
if (skillType == SKILL_TYPE_INDEPENDENT) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "independent skill not supported yet");
return ERR_TOOL_NOT_EXIST;
}
return AAFwk::AbilityManagerClient::GetInstance()->ExecuteInAppSkill(
bundleName, moduleName, skillName, arkTSPath, funcName, skillArgs, callback);
}
} // namespace
void JSSkillDriver::Finalizer(napi_env env, void *data, void *hint)
{
TAG_LOGD(AAFwkTag::CLI_TOOL, "JSSkillDriver::Finalizer is called");
std::unique_ptr<JSSkillDriver>(static_cast<JSSkillDriver *>(data));
}
napi_value JSSkillDriver::ExecSkillTool(napi_env env, napi_callback_info info)
{
GET_CB_INFO_AND_CALL(env, info, JSSkillDriver, OnExecSkillTool);
}
napi_value JSSkillDriver::OnExecSkillTool(napi_env env, size_t argc, napi_value *argv)
{
TAG_LOGD(AAFwkTag::CLI_TOOL, "JSSkillDriver::OnExecSkillTool called");
HandleEscape handleEscape(env);
if (argc < INDEX_TWO) {
ThrowTooFewParametersError(env);
return CreateJsUndefined(env);
}
napi_valuetype valueType = napi_undefined;
napi_typeof(env, argv[INDEX_ZERO], &valueType);
if (valueType != napi_object) {
ThrowInvalidParamError(env, "skillToolParam must be an object");
return CreateJsUndefined(env);
}
auto skillToolType = GetStringPropertyFromJs(env, argv[INDEX_ZERO], "skillToolType");
if (skillToolType.empty()) {
ThrowInvalidParamError(env, "skillToolType is required");
return CreateJsUndefined(env);
}
auto bundleName = GetStringPropertyFromJs(env, argv[INDEX_ZERO], "bundleName");
auto moduleName = GetStringPropertyFromJs(env, argv[INDEX_ZERO], "moduleName");
auto skillName = GetStringPropertyFromJs(env, argv[INDEX_ZERO], "skillName");
if (bundleName.empty() || moduleName.empty() || skillName.empty()) {
ThrowInvalidParamError(env, "bundleName, moduleName, skillName are required");
return CreateJsUndefined(env);
}
auto arkTSPath = GetStringPropertyFromJs(env, argv[INDEX_ZERO], "arkTSPath");
auto funcName = GetStringPropertyFromJs(env, argv[INDEX_ZERO], "funcName");
auto skillArgs = ExtractSkillArgs(env, argv[INDEX_ZERO]);
TAG_LOGD(AAFwkTag::CLI_TOOL,
"execSkillTool bundle:%{public}s module:%{public}s skill:%{public}s "
"type:%{public}s",
bundleName.c_str(), moduleName.c_str(), skillName.c_str(),
skillToolType.c_str());
napi_deferred deferred = nullptr;
napi_value promise = nullptr;
napi_create_promise(env, &deferred, &promise);
auto innerErrCode = std::make_shared<int32_t>(ERR_OK);
auto callback = sptr<SkillExecuteCallbackImpl>::MakeSptr(env, deferred);
NapiAsyncTask::ExecuteCallback execute =
[innerErrCode, bundleName, moduleName, skillName,
arkTSPath, funcName, skillArgs, callback]() {
*innerErrCode = DispatchExecuteSkill(
bundleName, moduleName, skillName, arkTSPath, funcName, skillArgs, callback);
};
NapiAsyncTask::CompleteCallback complete =
[innerErrCode, deferred](napi_env env, NapiAsyncTask &task, int32_t status) {
HandleScope handleScope(env);
if (*innerErrCode != ERR_OK) {
napi_reject_deferred(env, deferred,
CreateCliJsErrorByNativeErr(env, *innerErrCode));
}
};
auto asyncTask = std::make_unique<NapiAsyncTask>(deferred,
std::make_unique<NapiAsyncTask::ExecuteCallback>(std::move(execute)),
std::make_unique<NapiAsyncTask::CompleteCallback>(std::move(complete)));
NapiAsyncTask::Schedule("JSSkillDriver::OnExecSkillTool", env, std::move(asyncTask));
return handleEscape.Escape(promise);
}
napi_value JSSkillDriverInit(napi_env env, napi_value exportObj)
{
TAG_LOGD(AAFwkTag::CLI_TOOL, "Init JSSkillDriver");
if (env == nullptr || exportObj == nullptr) {
TAG_LOGW(AAFwkTag::CLI_TOOL, "Null env or exportObj");
return nullptr;
}
std::unique_ptr<JSSkillDriver> jsSkillDriver = std::make_unique<JSSkillDriver>();
napi_wrap(env, exportObj, jsSkillDriver.release(), JSSkillDriver::Finalizer, nullptr, nullptr);
const char *moduleName = "SkillDriver";
BindNativeFunction(env, exportObj, "execSkillTool", moduleName, JSSkillDriver::ExecSkillTool);
TAG_LOGD(AAFwkTag::CLI_TOOL, "JSSkillDriverInit end");
return CreateJsUndefined(env);
}
} // namespace CliTool
} // namespace OHOS
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2026 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 "native_engine/native_engine.h"
#include "js_skill_driver.h"
static napi_module _module = {
.nm_version = 0,
.nm_filename = "app/ability/skilldriver_napi.so/skill_driver.js",
.nm_register_func = OHOS::CliTool::JSSkillDriverInit,
.nm_modname = "app.ability.skillDriver",
};
extern "C" __attribute__((constructor)) void NAPI_application_SkillDriver_AutoRegister(void)
{
napi_module_register(&_module);
}
@@ -19,10 +19,9 @@ idl_gen_interface("cli_tool_manager_interface") {
sources = [
"ICliToolManager.idl",
"ICliToolManagerScheduler.idl",
"IExecToolCallback.idl",
]
sources_common = [ "ICliToolCmd.idl" ]
sources_common = [ "ICliToolData.idl" ]
hitrace = "HITRACE_TAG_ABILITY_MANAGER"
log_domainid = "0xD001365"
log_tag = "CliToolManager"
@@ -51,7 +50,6 @@ ohos_shared_library("cli_tool_client") {
public_configs = [ ":cli_tool_client_config" ]
sources = [
"src/arg_mapping.cpp",
"src/cli_mgr_load_callback.cpp",
"src/cli_event_reply_manager.cpp",
"src/cli_session_info.cpp",
@@ -61,7 +59,6 @@ ohos_shared_library("cli_tool_client") {
"src/cli_tool_mgr_scheduler_recipient.cpp",
"src/exec_options.cpp",
"src/exec_result.cpp",
"src/exec_tool_callback_impl.cpp",
"src/exec_tool_param.cpp",
"src/sub_command_info.cpp",
"src/tool_info.cpp",
@@ -15,19 +15,18 @@
package OHOS.CliTool;
import ICliToolCmd;
import ICliToolData;
import ICliToolManagerScheduler;
sequenceable CliSessionInfo..OHOS.CliTool.CliSessionInfo;
sequenceable ExecToolParam..OHOS.CliTool.ExecToolParam;
sequenceable OHOS.CliTool.ToolSummary;
sequenceable OHOS.IRemoteObject;
sequenceable ToolInfo..OHOS.CliTool.ToolInfo;
rawdata ToolInfo..OHOS.CliTool.ToolsRawData;
interface ICliToolManager {
void GetAllToolSummaries([out] ToolSummary[] summaries);
void GetToolInfoByName([in] String name, [out] ToolInfo tool);
void GetAllToolInfos([out] ToolInfo[] tools);
void GetAllToolInfos([out] ToolsRawData tools);
void RegisterTool([in] ToolInfo tool);
void ExecTool([in] ExecToolParam param, [in] String eventId);
void SubscribeSession([in] String sessionId, [in] String subscriptionId);
@@ -15,8 +15,8 @@
package OHOS.CliTool;
sequenceable CliToolEvent..OHOS.CliTool.CliToolEvent;
sequenceable CliSessionInfo..OHOS.CliTool.CliSessionInfo;
sequenceable CliToolEvent..OHOS.CliTool.CliToolEvent;
interface ICliToolManagerScheduler {
[oneway]void SchedulerSessionEvent([in] String sessionId, [in] String subscriptionId, [in] CliToolEvent event);
@@ -1,78 +0,0 @@
/*
* Copyright (c) 2026 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.
*/
#ifndef OHOS_ABILITY_RUNTIME_ARG_MAPPING_H
#define OHOS_ABILITY_RUNTIME_ARG_MAPPING_H
#include <memory>
#include <nlohmann/json.hpp>
#include <parcel.h>
#include <string>
namespace OHOS {
namespace CliTool {
/**
* @brief Enum for argument mapping type
*/
enum class ArgMappingType {
FLAG = 0,
POSITIONAL = 1,
FLATTENED = 2,
JSONSTRING = 3,
MIXED = 4
};
/**
* @brief Argument mapping structure
*/
class ArgMapping : public Parcelable {
public:
ArgMappingType type = ArgMappingType::FLAG;
std::string separator;
std::string order;
std::string templates; // JSON string
ArgMapping() = default;
~ArgMapping() = default;
bool Marshalling(Parcel &parcel) const override;
static ArgMapping *Unmarshalling(Parcel &parcel);
/**
* @brief Parse ArgMapping from JSON object
* @param json Input JSON object
* @param argMapping Output ArgMapping
* @return bool true if parse success and required fields are valid
*/
static bool ParseFromJson(const nlohmann::json &json, ArgMapping &argMapping);
/**
* @brief Convert ArgMapping to JSON object
*/
nlohmann::json ParseToJson() const;
/**
* @brief Validate ArgMapping fields
* @param argMapping ArgMapping to validate
* @return bool true if valid
*/
static bool Validate(const ArgMapping &argMapping);
};
} // namespace CliTool
} // namespace OHOS
#endif // OHOS_ABILITY_RUNTIME_ARG_MAPPING_H
@@ -75,6 +75,11 @@ enum {
* Result (35700011): The caller is not SA.
*/
ERR_NOT_SA_CALLER = 35700011,
/*
* Result (35700012): fail to kill.
*/
ERR_NOT_KILL = 35700012,
};
} // namespace CliTool
@@ -32,7 +32,7 @@ public:
std::string sessionId;
std::string toolName;
std::string status; // "running", "completed", "failed"
std::shared_ptr<ExecResult> result = nullptr; // optional, only when status="completed"
std::shared_ptr<ExecResult> result = nullptr; // optional, only when status="completed" and status="failed"
CliSessionInfo() = default;
@@ -41,4 +41,4 @@ public:
};
} // namespace CliTool
} // namespace OHOS
#endif // OHOS_ABILITY_RUNTIME_CLI_SESSION_INFO_H
#endif // OHOS_ABILITY_RUNTIME_CLI_SESSION_INFO_H
@@ -30,8 +30,8 @@ namespace CliTool {
class ExecOptions : public Parcelable {
public:
bool background = false;
int32_t yieldMs = 0;
int32_t timeout = 0;
int64_t yieldMs = 0;
int64_t timeout = 0;
bool Marshalling(Parcel &parcel) const;
static ExecOptions *Unmarshalling(Parcel &parcel);
@@ -27,7 +27,7 @@ namespace CliTool {
*/
class ExecResult : public Parcelable {
public:
int32_t exitCode = -1;
int32_t exitCode = 1;
std::string outputText = "";
std::string errorText = "";
int32_t signalNumber = 0;
@@ -39,4 +39,4 @@ public:
};
} // namespace CliTool
} // namespace OHOS
#endif // OHOS_ABILITY_RUNTIME_EXEC_RESULT_H
#endif // OHOS_ABILITY_RUNTIME_EXEC_RESULT_H
@@ -1,42 +0,0 @@
/*
* Copyright (c) 2026 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.
*/
#ifndef OHOS_ABILITY_RUNTIME_EXEC_TOOL_CALLBACK_IMPL_H
#define OHOS_ABILITY_RUNTIME_EXEC_TOOL_CALLBACK_IMPL_H
#include <functional>
#include "cli_session_info.h"
#include "exec_tool_callback_stub.h"
namespace OHOS {
namespace CliTool {
namespace {
using ExecToolResultTask = std::function<void(const CliSessionInfo &session)>;
}
class ExecToolCallbackImpl : public ExecToolCallbackStub {
public:
explicit ExecToolCallbackImpl(ExecToolResultTask &&task) : task_(task) {}
virtual ~ExecToolCallbackImpl() = default;
int32_t SendResult(const CliSessionInfo &session) override;
private:
ExecToolResultTask task_;
};
} // namespace CliTool
} // namespace OHOS
#endif // OHOS_ABILITY_RUNTIME_EXEC_TOOL_CALLBACK_IMPL_H
@@ -16,8 +16,6 @@
#ifndef OHOS_ABILITY_RUNTIME_SUB_COMMAND_INFO_H
#define OHOS_ABILITY_RUNTIME_SUB_COMMAND_INFO_H
#include "arg_mapping.h"
#include <memory>
#include <nlohmann/json.hpp>
#include <parcel.h>
@@ -36,7 +34,6 @@ public:
std::vector<std::string> requirePermissions;
std::string inputSchema; // JSON string
std::string outputSchema; // JSON string
std::shared_ptr<ArgMapping> argMapping;
std::vector<std::string> eventTypes;
std::string eventSchemas; // JSON string
@@ -16,7 +16,6 @@
#ifndef OHOS_ABILITY_RUNTIME_TOOL_INFO_H
#define OHOS_ABILITY_RUNTIME_TOOL_INFO_H
#include "arg_mapping.h"
#include "sub_command_info.h"
#include "tool_summary.h"
@@ -29,38 +28,25 @@
#include <string>
#include <vector>
#include "exec_result.h"
namespace OHOS {
namespace CliTool {
class ToolInfo;
/**
* @brief Raw data type for IDL serialization
* @brief Raw data type for IDL serialization (shared memory optimization)
*/
class ToolsRawData : public Parcelable {
class ToolsRawData {
public:
std::vector<uint32_t> data;
std::string ownedData;
uint32_t size = 0;
const void* data = nullptr;
bool isMalloc = false;
ToolsRawData() = default;
~ToolsRawData() = default;
bool Marshalling(Parcel &parcel) const override
{
if (!parcel.WriteUInt32Vector(data)) {
return false;
}
return true;
}
static ToolsRawData *Unmarshalling(Parcel &parcel)
{
ToolsRawData *rawdata = new (std::nothrow) ToolsRawData();
if (rawdata && !parcel.ReadUInt32Vector(&rawdata->data)) {
delete rawdata;
return nullptr;
}
return rawdata;
}
static void FromToolInfoVec(const std::vector<ToolInfo> &tools, ToolsRawData &rawData);
static int32_t ToToolInfoVec(const ToolsRawData &rawData, std::vector<ToolInfo> &tools);
int32_t RawDataCpy(const void *readdata);
~ToolsRawData();
};
/**
@@ -75,10 +61,8 @@ public:
std::vector<std::string> requirePermissions;
std::string inputSchema; // JSON string
std::string outputSchema; // JSON string
std::shared_ptr<ArgMapping> argMapping;
std::vector<std::string> eventTypes;
std::string eventSchemas; // JSON string (map of event type to schema)
int32_t timeout = 1800;
bool hasSubCommand = false;
std::map<std::string, SubCommandInfo> subcommands;
@@ -1,160 +0,0 @@
/*
* Copyright (c) 2026 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 "arg_mapping.h"
namespace OHOS {
namespace CliTool {
bool ArgMapping::Marshalling(Parcel &parcel) const
{
if (!parcel.WriteInt32(static_cast<int32_t>(type))) {
return false;
}
if (!parcel.WriteString(separator)) {
return false;
}
if (!parcel.WriteString(order)) {
return false;
}
if (!parcel.WriteString(templates)) {
return false;
}
return true;
}
ArgMapping *ArgMapping::Unmarshalling(Parcel &parcel)
{
auto mapping = std::make_unique<ArgMapping>();
int32_t typeValue = 0;
if (!parcel.ReadInt32(typeValue)) {
return nullptr;
}
if (!parcel.ReadString(mapping->separator)) {
return nullptr;
}
if (!parcel.ReadString(mapping->order)) {
return nullptr;
}
if (!parcel.ReadString(mapping->templates)) {
return nullptr;
}
mapping->type = static_cast<ArgMappingType>(typeValue);
return mapping.release();
}
bool ArgMapping::ParseFromJson(const nlohmann::json &json, ArgMapping &argMapping)
{
// type is required
if (!json.contains("type") || !json["type"].is_string()) {
return false;
}
std::string typeStr = json["type"];
if (typeStr == "flag") {
argMapping.type = ArgMappingType::FLAG;
} else if (typeStr == "positional") {
argMapping.type = ArgMappingType::POSITIONAL;
} else if (typeStr == "flattened") {
argMapping.type = ArgMappingType::FLATTENED;
} else if (typeStr == "jsonString") {
argMapping.type = ArgMappingType::JSONSTRING;
} else if (typeStr == "mixed") {
argMapping.type = ArgMappingType::MIXED;
} else {
return false; // invalid type value
}
if (json.contains("separator")) {
if (!json["separator"].is_string()) {
return false; // separator must be a string
}
argMapping.separator = json["separator"];
}
if (json.contains("order")) {
if (!json["order"].is_string()) {
return false; // order must be a string
}
argMapping.order = json["order"];
}
if (json.contains("templates")) {
if (!json["templates"].is_object()) {
return false; // templates must be an object
}
argMapping.templates = json["templates"].dump();
}
return true;
}
nlohmann::json ArgMapping::ParseToJson() const
{
nlohmann::json j;
switch (type) {
case ArgMappingType::FLAG:
j["type"] = "flag";
break;
case ArgMappingType::POSITIONAL:
j["type"] = "positional";
break;
case ArgMappingType::FLATTENED:
j["type"] = "flattened";
break;
case ArgMappingType::JSONSTRING:
j["type"] = "jsonString";
break;
case ArgMappingType::MIXED:
j["type"] = "mixed";
break;
}
if (!separator.empty()) {
j["separator"] = separator;
}
if (!order.empty()) {
j["order"] = order;
}
if (!templates.empty()) {
nlohmann::json templatesJson = nlohmann::json::parse(templates, nullptr, false);
if (!templatesJson.is_discarded()) {
j["templates"] = templatesJson;
} else {
j["templates"] = templates;
}
}
return j;
}
bool ArgMapping::Validate(const ArgMapping &argMapping)
{
// type must be valid enum value
int32_t typeValue = static_cast<int32_t>(argMapping.type);
if (typeValue < 0 || typeValue > static_cast<int32_t>(ArgMappingType::MIXED)) {
return false;
}
// templates must be valid JSON object if not empty
if (!argMapping.templates.empty()) {
nlohmann::json templatesJson = nlohmann::json::parse(argMapping.templates, nullptr, false);
if (templatesJson.is_discarded() || !templatesJson.is_object()) {
return false;
}
}
return true;
}
} // namespace CliTool
} // namespace OHOS
@@ -31,7 +31,6 @@ bool CliSessionInfo::Marshalling(Parcel &parcel) const
return false;
}
// Write result presence flag
bool hasResult = (result != nullptr);
if (!parcel.WriteBool(hasResult)) {
return false;
@@ -16,16 +16,15 @@
#include "cli_tool_mgr_client.h"
#include "cli_error_code.h"
#include "cli_event_reply_manager.h"
#include "cli_mgr_load_callback.h"
#include "cli_session_subscription_manager.h"
#include "cli_tool_mgr_scheduler_recipient.h"
#include "hilog_tag_wrapper.h"
#include "hitrace_meter.h"
#include "if_system_ability_manager.h"
#include "iservice_registry.h"
#include "system_ability_definition.h"
#include "cli_mgr_load_callback.h"
#include "cli_event_reply_manager.h"
#include "cli_session_subscription_manager.h"
#include "cli_tool_mgr_scheduler_recipient.h"
namespace OHOS {
namespace CliTool {
@@ -130,7 +129,13 @@ ErrCode CliToolMGRClient::GetAllToolInfos(std::vector<ToolInfo> &tools)
TAG_LOGE(AAFwkTag::CLI_TOOL, "proxy is null");
return GET_CLI_TOOL_MGR_SERVICE_FAILED;
}
return proxy->GetAllToolInfos(tools);
ToolsRawData rawData;
auto ret = proxy->GetAllToolInfos(rawData);
if (ret != ERR_OK) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "GetAllToolInfos failed: %{public}d", ret);
return ret;
}
return ToolsRawData::ToToolInfoVec(rawData, tools);
}
ErrCode CliToolMGRClient::RegisterTool(const ToolInfo &tool)
@@ -196,7 +201,8 @@ sptr<ICliToolManager> CliToolMGRClient::GetCliToolMgrProxy()
const auto &onClearProxyCallback = [](const wptr<IRemoteObject> &remote) {
auto &instance = GetInstance();
if (instance.cliToolMgr_ == remote) {
auto cliToolMgr = instance.GetCliToolMgr();
if (cliToolMgr != nullptr && cliToolMgr->AsObject() == remote) {
instance.ClearProxy();
}
};
@@ -281,7 +287,7 @@ void CliToolMGRClient::ClearProxy()
void CliToolMGRClient::CliMgrDeathRecipient::OnRemoteDied(const wptr<IRemoteObject> &remote)
{
if (callback_ != nullptr) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "cli tool manager service died");
TAG_LOGI(AAFwkTag::CLI_TOOL, "cli tool manager service died");
callback_(remote);
}
}
@@ -22,10 +22,10 @@ bool ExecOptions::Marshalling(Parcel &parcel) const
if (!parcel.WriteBool(background)) {
return false;
}
if (!parcel.WriteInt32(yieldMs)) {
if (!parcel.WriteInt64(yieldMs)) {
return false;
}
if (!parcel.WriteInt32(timeout)) {
if (!parcel.WriteInt64(timeout)) {
return false;
}
return true;
@@ -34,15 +34,18 @@ bool ExecOptions::Marshalling(Parcel &parcel) const
ExecOptions *ExecOptions::Unmarshalling(Parcel &parcel)
{
auto *options = new (std::nothrow) ExecOptions();
if (options && !parcel.ReadBool(options->background)) {
if (options == nullptr) {
return nullptr;
}
if (!parcel.ReadBool(options->background)) {
delete options;
return nullptr;
}
if (!parcel.ReadInt32(options->yieldMs)) {
if (!parcel.ReadInt64(options->yieldMs)) {
delete options;
return nullptr;
}
if (!parcel.ReadInt32(options->timeout)) {
if (!parcel.ReadInt64(options->timeout)) {
delete options;
return nullptr;
}
@@ -38,14 +38,6 @@ bool SubCommandInfo::Marshalling(Parcel &parcel) const
TAG_LOGE(AAFwkTag::CLI_TOOL, "Failed to write outputSchema");
return false;
}
if (!parcel.WriteBool(argMapping != nullptr)) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Failed to write hasArgMapping flag");
return false;
}
if (argMapping != nullptr && !argMapping->Marshalling(parcel)) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Failed to write argMapping");
return false;
}
if (!parcel.WriteStringVector(eventTypes)) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Failed to write eventTypes");
return false;
@@ -65,7 +57,6 @@ SubCommandInfo *SubCommandInfo::Unmarshalling(Parcel &parcel)
return nullptr;
}
bool hasArgMapping = false;
if (!parcel.ReadString(subCmd->description)) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Failed to read description");
delete subCmd;
@@ -86,21 +77,6 @@ SubCommandInfo *SubCommandInfo::Unmarshalling(Parcel &parcel)
delete subCmd;
return nullptr;
}
if (!parcel.ReadBool(hasArgMapping)) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Failed to read hasArgMapping flag");
delete subCmd;
return nullptr;
}
if (hasArgMapping) {
subCmd->argMapping.reset(ArgMapping::Unmarshalling(parcel));
if (subCmd->argMapping == nullptr) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Failed to unmarshal argMapping");
delete subCmd;
return nullptr;
}
}
if (!parcel.ReadStringVector(&subCmd->eventTypes)) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Failed to read eventTypes");
delete subCmd;
@@ -119,59 +95,55 @@ bool SubCommandInfo::ParseFromJson(const nlohmann::json &json, SubCommandInfo &s
{
// description is required and must be non-empty
if (!json.contains("description") || !json["description"].is_string()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "ParseFromJson failed: description is missing or not a string");
return false;
}
std::string description = json["description"];
if (description.empty()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "ParseFromJson failed: description is empty");
return false;
}
subCmd.description = description;
// requirePermissions is optional, but if present must be array of strings
if (json.contains("requirePermissions")) {
if (!json["requirePermissions"].is_array()) {
// requirePermissions is required and must be array
if (!json.contains("requirePermissions") || !json["requirePermissions"].is_array()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "ParseFromJson failed: requirePermissions is missing or not an array");
return false;
}
for (const auto &perm : json["requirePermissions"]) {
if (!perm.is_string()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "ParseFromJson failed: requirePermissions contains non-string item");
return false;
}
for (const auto &perm : json["requirePermissions"]) {
if (!perm.is_string()) {
return false;
}
std::string permStr = perm;
if (!permStr.empty()) {
subCmd.requirePermissions.push_back(std::move(permStr));
}
std::string permStr = perm;
if (!permStr.empty()) {
subCmd.requirePermissions.push_back(std::move(permStr));
}
}
// inputSchema is required and must be JSON object
if (!json.contains("inputSchema") || !json["inputSchema"].is_object()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "ParseFromJson failed: inputSchema is missing or not a JSON object");
return false;
}
subCmd.inputSchema = json["inputSchema"].dump();
// outputSchema is required and must be JSON object
if (!json.contains("outputSchema") || !json["outputSchema"].is_object()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "ParseFromJson failed: outputSchema is missing or not a JSON object");
return false;
}
subCmd.outputSchema = json["outputSchema"].dump();
// argMapping is required
if (!json.contains("argMapping") || !json["argMapping"].is_object()) {
return false;
}
subCmd.argMapping = std::make_shared<ArgMapping>();
if (!ArgMapping::ParseFromJson(json["argMapping"], *subCmd.argMapping)) {
subCmd.argMapping = nullptr;
return false; // argMapping parse failed
}
// eventTypes is optional, but if present must be array of strings
if (json.contains("eventTypes")) {
if (!json["eventTypes"].is_array()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "ParseFromJson failed: eventTypes is not an array");
return false;
}
for (const auto &evt : json["eventTypes"]) {
if (!evt.is_string()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "ParseFromJson failed: eventTypes contains non-string item");
return false;
}
std::string evtStr = evt;
@@ -184,6 +156,7 @@ bool SubCommandInfo::ParseFromJson(const nlohmann::json &json, SubCommandInfo &s
// eventSchemas is optional, but if present must be JSON object
if (json.contains("eventSchemas")) {
if (!json["eventSchemas"].is_object()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "ParseFromJson failed: eventSchemas is not a JSON object");
return false;
}
subCmd.eventSchemas = json["eventSchemas"].dump();
@@ -223,9 +196,6 @@ nlohmann::json SubCommandInfo::ParseToJson() const
json["eventSchemas"] = eventSchemas;
}
}
if (argMapping != nullptr) {
json["argMapping"] = argMapping->ParseToJson();
}
return json;
}
@@ -260,16 +230,6 @@ bool SubCommandInfo::Validate(const SubCommandInfo &subCmd)
return false;
}
// argMapping is required
if (subCmd.argMapping == nullptr) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Validate failed: argMapping is null");
return false;
}
if (!ArgMapping::Validate(*subCmd.argMapping)) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Validate failed: argMapping validation failed");
return false;
}
// eventSchemas: if not empty, must be valid JSON object
if (!subCmd.eventSchemas.empty()) {
nlohmann::json eventSchemasJson = nlohmann::json::parse(subCmd.eventSchemas, nullptr, false);
@@ -17,12 +17,18 @@
#include <nlohmann/json.hpp>
#include <set>
#include <sstream>
#include "hilog_tag_wrapper.h"
#include "securec.h"
namespace OHOS {
namespace CliTool {
namespace {
constexpr uint32_t MAX_TOOL_INFO_COUNT = 200000;
} // namespace
// ToolInfo implementation
bool ToolInfo::Marshalling(Parcel &parcel) const
{
@@ -43,10 +49,7 @@ bool ToolInfo::Marshalling(Parcel &parcel) const
parcel.WriteStringVector(requirePermissions) &&
parcel.WriteString(inputSchema) &&
parcel.WriteString(outputSchema) &&
parcel.WriteBool(argMapping != nullptr) &&
(argMapping == nullptr || argMapping->Marshalling(parcel)) &&
parcel.WriteString(eventSchemas) &&
parcel.WriteInt32(timeout) &&
parcel.WriteStringVector(eventTypes) &&
parcel.WriteBool(hasSubCommand) &&
parcel.WriteString(subcommandsJson);
@@ -59,7 +62,6 @@ ToolInfo *ToolInfo::Unmarshalling(Parcel &parcel)
return nullptr;
}
bool hasArgMapping = false;
std::string subcommandsJson;
if (!parcel.ReadString(tool->name) ||
!parcel.ReadString(tool->version) ||
@@ -68,21 +70,7 @@ ToolInfo *ToolInfo::Unmarshalling(Parcel &parcel)
!parcel.ReadStringVector(&tool->requirePermissions) ||
!parcel.ReadString(tool->inputSchema) ||
!parcel.ReadString(tool->outputSchema) ||
!parcel.ReadBool(hasArgMapping)) {
delete tool;
return nullptr;
}
if (hasArgMapping) {
tool->argMapping.reset(ArgMapping::Unmarshalling(parcel));
if (tool->argMapping == nullptr) {
delete tool;
return nullptr;
}
}
if (!parcel.ReadString(tool->eventSchemas) ||
!parcel.ReadInt32(tool->timeout) ||
!parcel.ReadString(tool->eventSchemas) ||
!parcel.ReadStringVector(&tool->eventTypes) ||
!parcel.ReadBool(tool->hasSubCommand) ||
!parcel.ReadString(subcommandsJson)) {
@@ -117,7 +105,7 @@ bool ToolInfo::ValidateName(const std::string &name)
// Must start with "ohos-" or "hms-"
const std::string OHOS_PREFIX = "ohos-";
const std::string HMS_PREFIX = "hms-";
const size_t MAX_SUFFIX_LENGTH = 16;
const size_t MAX_SUFFIX_LENGTH = 32;
bool hasValidPrefix = false;
size_t suffixStart = 0;
@@ -134,7 +122,7 @@ bool ToolInfo::ValidateName(const std::string &name)
return false;
}
// Suffix must not exceed 16 characters
// Suffix must not exceed 32 characters
std::string suffix = name.substr(suffixStart);
if (suffix.empty() || suffix.size() > MAX_SUFFIX_LENGTH) {
return false;
@@ -247,44 +235,38 @@ bool ToolInfo::ParseFromJson(const nlohmann::json &json, ToolInfo &tool)
}
tool.executablePath = executablePath;
if (json.contains("requirePermissions") && json["requirePermissions"].is_array()) {
std::vector<std::string> perms;
for (const auto &perm : json["requirePermissions"]) {
if (!perm.is_string()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "ParseFromJson failed: requirePermissions contains non-string item");
return false;
}
std::string permStr = perm.get<std::string>();
if (!permStr.empty()) {
perms.push_back(std::move(permStr));
}
}
tool.requirePermissions = std::move(perms);
}
if (json.contains("inputSchema")) {
if (!json["inputSchema"].is_object()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "ParseFromJson failed: inputSchema is not a JSON object");
return false;
}
tool.inputSchema = json["inputSchema"].dump();
}
if (json.contains("outputSchema")) {
if (!json["outputSchema"].is_object()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "ParseFromJson failed: outputSchema is not a JSON object");
return false;
}
tool.outputSchema = json["outputSchema"].dump();
}
if (!json.contains("argMapping") || !json["argMapping"].is_object()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "ParseFromJson failed: argMapping is required and must be an object");
// requirePermissions is required and must be array
if (!json.contains("requirePermissions") || !json["requirePermissions"].is_array()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "ParseFromJson failed: requirePermissions is missing or not an array");
return false;
}
tool.argMapping = std::make_shared<ArgMapping>();
if (!ArgMapping::ParseFromJson(json["argMapping"], *tool.argMapping)) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "ParseFromJson failed: argMapping parse failed");
tool.argMapping = nullptr;
std::vector<std::string> perms;
for (const auto &perm : json["requirePermissions"]) {
if (!perm.is_string()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "ParseFromJson failed: requirePermissions contains non-string item");
return false;
}
std::string permStr = perm.get<std::string>();
if (!permStr.empty()) {
perms.push_back(std::move(permStr));
}
}
tool.requirePermissions = std::move(perms);
// inputSchema is required and must be a JSON object
if (!json.contains("inputSchema") || !json["inputSchema"].is_object()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "ParseFromJson failed: inputSchema is missing or not a JSON object");
return false;
}
tool.inputSchema = json["inputSchema"].dump();
// outputSchema is required and must be a JSON object
if (!json.contains("outputSchema") || !json["outputSchema"].is_object()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "ParseFromJson failed: outputSchema is missing or not a JSON object");
return false;
}
tool.outputSchema = json["outputSchema"].dump();
if (json.contains("eventSchemas")) {
if (!json["eventSchemas"].is_object()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "ParseFromJson failed: eventSchemas is not a JSON object");
@@ -292,19 +274,6 @@ bool ToolInfo::ParseFromJson(const nlohmann::json &json, ToolInfo &tool)
}
tool.eventSchemas = json["eventSchemas"].dump();
}
if (json.contains("timeout")) {
if (!json["timeout"].is_number_integer()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "ParseFromJson failed: timeout is not an integer");
return false;
}
int32_t timeoutValue = json["timeout"];
if (timeoutValue <= 0 || timeoutValue > 1800) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "ParseFromJson failed: timeout %{public}d is out of range (0, 1800]",
timeoutValue);
return false;
}
tool.timeout = timeoutValue;
}
if (json.contains("eventTypes")) {
if (!json["eventTypes"].is_array()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "ParseFromJson failed: eventTypes is not an array");
@@ -374,9 +343,6 @@ nlohmann::json ToolInfo::ParseToJson() const
j["outputSchema"] = outputSchema;
}
}
if (argMapping != nullptr) {
j["argMapping"] = argMapping->ParseToJson();
}
if (!eventSchemas.empty()) {
nlohmann::json eventSchemasJson = nlohmann::json::parse(eventSchemas, nullptr, false);
if (!eventSchemasJson.is_discarded()) {
@@ -385,7 +351,6 @@ nlohmann::json ToolInfo::ParseToJson() const
j["eventSchemas"] = eventSchemas;
}
}
j["timeout"] = timeout;
j["eventTypes"] = eventTypes;
j["hasSubCommand"] = hasSubCommand;
if (!subcommands.empty()) {
@@ -426,38 +391,25 @@ bool ToolInfo::Validate(const ToolInfo &tool)
return false;
}
// inputSchema: if not empty, must be valid JSON string
if (!tool.inputSchema.empty()) {
nlohmann::json inputSchemaJson = nlohmann::json::parse(tool.inputSchema, nullptr, false);
if (inputSchemaJson.is_discarded()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Validate failed: inputSchema is not valid JSON");
return false;
}
}
// outputSchema: if not empty, must be valid JSON string
if (!tool.outputSchema.empty()) {
nlohmann::json outputSchemaJson = nlohmann::json::parse(tool.outputSchema, nullptr, false);
if (outputSchemaJson.is_discarded()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Validate failed: outputSchema is not valid JSON");
return false;
}
}
// argMapping is required
if (tool.argMapping == nullptr) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Validate failed: argMapping is null");
// inputSchema is required and must be valid JSON string
if (tool.inputSchema.empty()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Validate failed: inputSchema is empty");
return false;
}
if (!ArgMapping::Validate(*tool.argMapping)) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Validate failed: argMapping validation failed");
nlohmann::json inputSchemaJson = nlohmann::json::parse(tool.inputSchema, nullptr, false);
if (inputSchemaJson.is_discarded()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Validate failed: inputSchema is not valid JSON");
return false;
}
// timeout must be > 0 and <= 1800
if (tool.timeout <= 0 || tool.timeout > 1800) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Validate failed: timeout %{public}d is out of range (0, 1800]",
tool.timeout);
// outputSchema is required and must be valid JSON string
if (tool.outputSchema.empty()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Validate failed: outputSchema is empty");
return false;
}
nlohmann::json outputSchemaJson = nlohmann::json::parse(tool.outputSchema, nullptr, false);
if (outputSchemaJson.is_discarded()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Validate failed: outputSchema is not valid JSON");
return false;
}
@@ -479,5 +431,94 @@ bool ToolInfo::Validate(const ToolInfo &tool)
return true;
}
ToolsRawData::~ToolsRawData()
{
if (data != nullptr && isMalloc) {
free(const_cast<void*>(data));
isMalloc = false;
data = nullptr;
}
}
int32_t ToolsRawData::RawDataCpy(const void *readdata)
{
if (readdata == nullptr || size == 0) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "null data or zero size");
return ERR_INVALID_VALUE;
}
void* newData = malloc(size);
if (newData == nullptr) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "malloc failed");
return ERR_INVALID_VALUE;
}
if (memcpy_s(newData, size, readdata, size) != EOK) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "memcpy_s failed");
free(newData);
return ERR_INVALID_VALUE;
}
if (data != nullptr && isMalloc) {
free(const_cast<void*>(data));
data = nullptr;
}
data = newData;
isMalloc = true;
return ERR_OK;
}
void ToolsRawData::FromToolInfoVec(const std::vector<ToolInfo> &tools, ToolsRawData &rawData)
{
std::stringstream ss;
uint32_t count = tools.size();
ss.write(reinterpret_cast<const char*>(&count), sizeof(count));
for (uint32_t i = 0; i < count; ++i) {
std::string dumped = tools[i].ParseToJson().dump();
uint32_t strLen = dumped.length();
ss.write(reinterpret_cast<const char*>(&strLen), sizeof(strLen));
ss.write(dumped.c_str(), strLen);
}
std::string result = ss.str();
rawData.ownedData = std::move(result);
rawData.data = rawData.ownedData.data();
rawData.size = rawData.ownedData.size();
rawData.isMalloc = false;
}
int32_t ToolsRawData::ToToolInfoVec(const ToolsRawData &rawData, std::vector<ToolInfo> &tools)
{
std::stringstream ss;
ss.write(reinterpret_cast<const char *>(rawData.data), rawData.size);
ss.seekg(0, std::ios::beg);
uint32_t ssLength = static_cast<uint32_t>(ss.str().length());
uint32_t count = 0;
ss.read(reinterpret_cast<char *>(&count), sizeof(count));
if (count > MAX_TOOL_INFO_COUNT) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "tools exceed maxSize %{public}d, count: %{public}d",
MAX_TOOL_INFO_COUNT, count);
return ERR_INVALID_VALUE;
}
tools.resize(count);
for (uint32_t i = 0; i < count; ++i) {
uint32_t toolSize = 0;
ss.read(reinterpret_cast<char *>(&toolSize), sizeof(toolSize));
if (toolSize > ssLength - static_cast<uint32_t>(ss.tellg())) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "toolSize:%{public}u is invalid", toolSize);
return ERR_INVALID_VALUE;
}
std::string toolStr(toolSize, '\0');
ss.read(toolStr.data(), toolSize);
nlohmann::json jsonObject = nlohmann::json::parse(toolStr, nullptr, false, true);
if (jsonObject.is_discarded()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "json parse failed, index: %{public}u", i);
return ERR_INVALID_VALUE;
}
if (!ToolInfo::ParseFromJson(jsonObject, tools[i])) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "ParseFromJson failed, index: %{public}u", i);
return ERR_INVALID_VALUE;
}
}
return ERR_OK;
}
} // namespace CliTool
} // namespace OHOS
@@ -39,8 +39,10 @@ ohos_shared_library("climgr") {
configs = [ ":clisa_config" ]
include_dirs = [ "${ability_runtime_path}/interfaces/kits/native/appkit/ability_bundle_manager_helper" ]
sources = [
"src/cli_tool_app_state_observer.cpp",
"src/cli_tool_data_manager.cpp",
"src/cli_tool_manager_service.cpp",
"src/permission_query_util.cpp",
"src/process_manager.cpp",
"src/session_record.cpp",
"src/tool_util.cpp",
@@ -52,6 +54,8 @@ ohos_shared_library("climgr") {
defines = [ "AMS_LOG_TAG = \"CliToolManager\"" ]
deps = [
"${ability_runtime_innerkits_path}/ability_manager:ability_manager",
"${ability_runtime_innerkits_path}/app_manager:app_manager",
"${ability_runtime_native_path}/appkit:appkit_manager_helper",
"${cli_tool_framework_path}/interfaces/cli_tool:cli_tool_client",
]
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2026 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.
*/
#ifndef OHOS_ABILITY_RUNTIME_CLI_TOOL_APP_STATE_OBSERVER_H
#define OHOS_ABILITY_RUNTIME_CLI_TOOL_APP_STATE_OBSERVER_H
#include <functional>
#include <string>
#include "application_state_observer_stub.h"
namespace OHOS {
namespace CliTool {
class CliToolAppStateObserver : public AppExecFwk::ApplicationStateObserverStub {
public:
using ProcessDiedCallback = std::function<void(const std::string&, pid_t)>;
explicit CliToolAppStateObserver(const std::string &bundleName, ProcessDiedCallback callback);
~CliToolAppStateObserver() override = default;
void OnProcessDied(const AppExecFwk::ProcessData &processData) override;
private:
std::string bundleName_;
ProcessDiedCallback processDiedCallback_;
};
} // namespace CliTool
} // namespace OHOS
#endif // OHOS_ABILITY_RUNTIME_CLI_TOOL_APP_STATE_OBSERVER_H
@@ -42,6 +42,13 @@ public:
*/
int32_t GetAllTools(std::vector<ToolInfo> &tools);
/**
* @brief Get all tools as raw data (shared memory optimization)
* @param rawData Output ToolsRawData
* @return int32_t ERR_OK on success, error code otherwise
*/
int32_t GetAllToolsRawData(ToolsRawData &rawData);
/**
* @brief Get tool by name from KVStore
* @param name Tool name
@@ -16,8 +16,11 @@
#ifndef OHOS_ABILITY_RUNTIME_CLI_TOOL_MGR_SERVICE_H
#define OHOS_ABILITY_RUNTIME_CLI_TOOL_MGR_SERVICE_H
#include <atomic>
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include "cli_tool_manager_stub.h"
@@ -30,15 +33,24 @@
#include "io_monitor.h"
#include "process_manager.h"
#include "session_record.h"
#include "skill/skill_execute_callback_stub.h"
namespace OHOS {
namespace AppExecFwk {
class IApplicationStateObserver;
struct SkillExecuteResult;
}
namespace CliTool {
class SessionRecord;
class SkillCallbackAdapter;
class CliToolManagerService;
class CliToolManagerService : public SystemAbility,
public CliToolManagerStub,
public std::enable_shared_from_this<CliToolManagerService> {
public CliToolManagerStub {
DECLARE_SYSTEM_ABILITY(CliToolManagerService);
friend class SkillCallbackAdapter;
public:
static sptr<CliToolManagerService> GetInstance();
virtual ~CliToolManagerService() = default;
@@ -46,7 +58,7 @@ public:
/**
* @brief Query all available tools
*/
int32_t GetAllToolInfos(std::vector<ToolInfo> &tools) override;
int32_t GetAllToolInfos(ToolsRawData &tools) override;
/**
* @brief Query tool summaries (lightweight for listing)
@@ -92,15 +104,34 @@ public:
protected:
void OnStart() override;
void OnStop() override;
int32_t OnIdle(const SystemAbilityOnDemandReason &idlReason) override;
private:
CliToolManagerService() : SystemAbility(CLI_TOOL_MGR_SERVICE_ID, false) {};
// RAII wrapper for interface call counter
class InterfaceCallCounter {
public:
explicit InterfaceCallCounter(std::atomic<int32_t>& counter) : counter_(counter)
{
counter_.fetch_add(1, std::memory_order_acq_rel);
}
~InterfaceCallCounter()
{
counter_.fetch_sub(1, std::memory_order_acq_rel);
}
InterfaceCallCounter(const InterfaceCallCounter&) = delete;
InterfaceCallCounter& operator=(const InterfaceCallCounter&) = delete;
private:
std::atomic<int32_t>& counter_;
};
enum class ServiceRunningState { STATE_NOT_START, STATE_RUNNING };
void Init();
void DelayUnloadTask();
std::shared_ptr<SessionRecord> CreateSessionRecord(const ExecToolParam &param);
std::shared_ptr<SessionRecord> CreateSessionRecord(const ExecToolParam &param, const std::string &eventId);
void AddSessionRecord(const std::shared_ptr<SessionRecord> &record);
std::shared_ptr<SessionRecord> GetSessionRecord(const std::string &sessionId);
void RemoveSessionRecord(const std::string &sessionId);
@@ -108,6 +139,23 @@ private:
bool RegisterSessionWithMonitors(const std::shared_ptr<SessionRecord> &record, const ExecToolParam &param);
void UnregisterSessionWithMonitors(const std::string &sessionId);
int32_t ValidateExecToolPermissions();
int32_t ValidateSessionLimit();
int32_t ValidateAndPrepareTool(const ExecToolParam &param, uint32_t tokenId,
ToolInfo &toolInfo, std::string &sandboxConfig, std::string &bundleName);
int32_t SetupAndStartSession(const ExecToolParam &param, const std::string &eventId,
const ToolInfo &toolInfo, const std::string &sandboxConfig, const std::string &bundleName);
int32_t SetupAndStartSkillSession(const ExecToolParam &param,
const std::string &eventId, const ToolInfo &toolInfo);
int32_t ValidateSkillTypeFromParam(const ExecToolParam &param, int32_t &skillType);
int32_t ValidateSkillType(const std::string &bundleName,
const std::string &moduleName, const std::string &skillName, int32_t &skillType);
void HandleSkillSessionComplete(const std::string &sessionId, int32_t callerPid,
const std::string &eventId, int32_t resultCode, const CliSessionInfo &session);
void HandleSkillSessionTimeout(const std::string &sessionId);
void HandleBackgroundSessionReply(const std::shared_ptr<SessionRecord> &record, const std::string &eventId);
void HandleProcessTimeout(const std::string &sessionId);
void HandleProcessYieldTimeout(const std::string &sessionId);
void HandleOutputClosed(const std::string &sessionId, bool isStdout);
@@ -121,31 +169,31 @@ private:
static void sigchld_handler(int32_t sig);
void PostExecToolTask(int32_t time, const std::string &sessionId, bool isTimeout);
void PostExecToolTask(int64_t time, const std::string &sessionId, bool isTimeout);
void WaitPid(pid_t pid, int32_t status, int32_t sig);
void RegisterAppStateObserver(const std::string &bundleName, pid_t callerPid);
void OnProcessDied(const std::string &bundleName, pid_t diedPid);
bool initialized_ = false;
std::shared_ptr<IOMonitor> ioMonitor_ = nullptr;
/**
* @brief Execute single command permission query.
*/
int32_t DoQueryPermission(const Command &cmd, std::vector<std::string> &permissions);
/**
* @brief Query main command permissions.
*/
int32_t QueryMainCommandPermission(const std::string &toolName, std::vector<std::string> &permissions);
/**
* @brief Query subcommand permissions.
*/
int32_t QuerySubCommandPermission(const std::string &toolName, const std::string &subCommand,
std::vector<std::string> &permissions);
std::atomic<int32_t> activeSessionCount_ = 0;
std::mutex sessionsMutex_;
std::atomic<int32_t> interfaceCalledCount_ = 0;
ffrt::mutex sessionsMutex_;
std::unordered_map<std::string, std::shared_ptr<SessionRecord>> sessionRecords_;
std::unordered_map<std::string, sptr<AppExecFwk::IApplicationStateObserver>> bundleObservers_;
};
class SkillCallbackAdapter : public AAFwk::SkillExecuteCallbackStub {
public:
SkillCallbackAdapter(const std::string &sessionId,
int32_t callerPid, const std::string &eventId);
void OnExecuteDone(const std::string &requestCode, int32_t resultCode,
const AppExecFwk::SkillExecuteResult &result) override;
private:
std::string sessionId_;
int32_t callerPid_;
std::string eventId_;
};
} // namespace CliTool
@@ -16,8 +16,10 @@
#ifndef OHOS_ABILITY_RUNTIME_CLI_TOOL_MGR_EVENT_DISPATCHER_H
#define OHOS_ABILITY_RUNTIME_CLI_TOOL_MGR_EVENT_DISPATCHER_H
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <sys/types.h>
#include <unordered_map>
#include <utility>
@@ -17,7 +17,9 @@
#define OHOS_ABILITY_RUNTIME_IO_MONITOR_H
#include <atomic>
#include <deque>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
@@ -57,8 +59,22 @@ private:
bool isStdin = false;
};
struct PendingInput {
std::string message;
std::string eventId;
};
struct InputQueue {
std::deque<PendingInput> pendingInputs;
size_t pendingBytes = 0;
bool writeTaskRunning = false;
};
int GetStdinFd(const std::string &sessionId);
void WriteTask(const std::string &sessionId, const std::string &message, const std::string &eventId);
int GetStdinFdLocked(const std::string &sessionId) const;
bool WriteMessage(int fd, const std::string &sessionId, const std::string &message);
void ProcessWriteQueue(const std::string &sessionId);
void NotifyInputReply(const std::string &sessionId, const std::string &eventId, bool result);
void MonitorLoop();
void HandleReadableFd(int fd);
@@ -70,6 +86,7 @@ private:
int epollFd_ = -1;
std::mutex fdMutex_;
std::unordered_map<int, FdInfo> fdMap_;
std::unordered_map<std::string, InputQueue> inputQueues_;
OutputCallback outputCallback_;
InputReplyCallback inputReplyCallback_;
SessionClosedCallback sessionClosedCallback_;
@@ -0,0 +1,105 @@
/*
* Copyright (c) 2026 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.
*/
#ifndef OHOS_ABILITY_RUNTIME_PERMISSION_QUERY_UTIL_H
#define OHOS_ABILITY_RUNTIME_PERMISSION_QUERY_UTIL_H
#include <string>
#include <vector>
#include "cli_error_code.h"
#include "icli_tool_data.h"
namespace OHOS {
namespace CliTool {
/**
* @brief Query result codes for batch permission query
*/
namespace QueryResult {
constexpr int32_t SUCCESS = 0;
constexpr int32_t COMMAND_NOT_EXIST = 1;
constexpr int32_t DB_ERROR = 2;
} // namespace QueryResult
/**
* @brief CLI tool permission query utility class
* Provides static methods to handle command permission query logic
*/
class PermissionQueryUtil {
public:
/**
* @brief Batch query command permissions
* @param cmds Command list to query
* @param cmdPermissions Output vector of CommandPermission query results
* @return ERR_OK on success
*/
static int32_t BatchQueryPermissions(
const std::vector<Command> &cmds,
std::vector<CommandPermission> &cmdPermissions);
private:
/**
* @brief Query permissions for a single command
* @param cmd Command to query
* @param permissions Output vector of permission strings
* @return ERR_OK on success
* ERR_TOOL_NOT_EXIST when tool not found
* ERR_NO_INIT on database error
*/
static int32_t QuerySingleCommand(
const Command &cmd,
std::vector<std::string> &permissions);
/**
* @brief Query permissions for main command (no subcommand)
* @param toolName Tool name
* @param permissions Output vector of permission strings
* @return ERR_OK on success
* ERR_TOOL_NOT_EXIST when tool not found
* ERR_NO_INIT on database error
*/
static int32_t QueryMainCommandPermission(
const std::string &toolName,
std::vector<std::string> &permissions);
/**
* @brief Query permissions for subcommand
* @param toolName Tool name
* @param subCommand Subcommand name
* @param permissions Output vector of permission strings
* @return ERR_OK on success
* ERR_TOOL_NOT_EXIST when tool or subcommand not found
* ERR_NO_INIT on database error
*/
static int32_t QuerySubCommandPermission(
const std::string &toolName,
const std::string &subCommand,
std::vector<std::string> &permissions);
/**
* @brief Build CommandPermission result object
* @param cmd Command
* @param permissions Permission list
* @param queryRet Query result code
* @return CommandPermission object
*/
static CommandPermission BuildCommandPermission(
const Command &cmd,
const std::vector<std::string> &permissions,
int32_t queryRet);
};
} // namespace CliTool
} // namespace OHOS
#endif // OHOS_ABILITY_RUNTIME_PERMISSION_QUERY_UTIL_H
@@ -40,7 +40,7 @@ public:
int32_t CreateChildProcess(const ExecToolParam &param, const std::string &sandboxConfig,
const ToolInfo &toolInfo, std::shared_ptr<SessionRecord> record) const;
bool TerminateProcess(pid_t pid, int signal = SIGTERM) const;
bool Killpg(pid_t pid) const;
private:
ProcessManager() = default;
@@ -30,10 +30,17 @@
namespace OHOS {
namespace CliTool {
enum class SessionType {
CLI = 0,
SKILL,
};
enum class SessionState {
SPAWNING = 0,
RUNNING,
CANCELLING,
COMPLETED,
FAILED,
};
class SessionRecord {
@@ -51,6 +58,7 @@ public:
int32_t stdinPipe[2] = {-1, -1}; // [0]=read, [1]=write
int32_t stdoutPipe[2] = {-1, -1};
int32_t stderrPipe[2] = {-1, -1};
SessionType sessionType = SessionType::CLI;
void SetState(SessionState state);
SessionState GetState() const;
@@ -58,6 +66,8 @@ public:
void SetTerminalResult(int32_t status, int32_t sig);
int32_t GetTerminalStatus() const;
void SetSkillResult(int32_t resultCode, const std::string &outputText);
void SetTimedOut(bool timedOut);
bool TimedOut() const;
int64_t GetEndTimeMs() const;
@@ -76,6 +86,11 @@ public:
void BuildSessionInfo(CliSessionInfo &session) const;
inline pid_t GetCallerPid()
{
return callerPid;
}
private:
void TrimBufferedOutput(std::string &buffer);
@@ -24,6 +24,8 @@
#include <iremote_object.h>
#include <functional>
#include "cli_session_info.h"
namespace OHOS {
namespace AAFwk {
class WantParams;
@@ -32,6 +34,7 @@ struct IArray;
}
namespace AppExecFwk {
struct BundleInfo;
struct SkillExecuteResult;
}
namespace CliTool {
class ExecToolParam;
@@ -47,13 +50,17 @@ public:
static std::string GenerateCliSessionId(const std::string &name, std::shared_ptr<SessionRecord> record);
static bool GenerateSandboxConfig(const std::string &challenge, AccessToken::AccessTokenID tokenId,
std::string &sandboxConfig);
static bool GenerateSandboxConfig(const ExecToolParam &param, AccessToken::AccessTokenID tokenId,
std::string &sandboxConfig, std::string &bundleName);
static void TransferToCmdParam(const ToolInfo &toolInfo, const AAFwk::WantParams &args, std::string &cmdLine);
// Path utilities (public for testing)
static std::vector<std::string> SplitPathBySeparator(const std::string &path, const std::string &separator);
static bool IsSkillTool(const std::string &toolName);
static void NormalizeSkillParamKeys(AAFwk::WantParams &args);
static void ExpandArgsJsonString(AAFwk::WantParams &args);
static std::shared_ptr<AAFwk::WantParams> FilterSkillArgs(const AAFwk::WantParams &args);
static CliSessionInfo BuildSkillSessionInfo(const std::string &sessionId,
int32_t resultCode, const AppExecFwk::SkillExecuteResult &skillResult);
private:
static bool GetBundleInfoByTokenId(AccessToken::AccessTokenID tokenId,
@@ -63,12 +70,8 @@ private:
// Helper methods for type validation
static bool ValidateParamType(const sptr<AAFwk::IInterface> &value, const std::string &expectedType,
const nlohmann::json &propertySchema, const std::string &key = "");
static bool ValidateNestedObject(const AAFwk::WantParams &nestedParams,
const nlohmann::json &objectSchema, const std::string &parentKey);
static bool ValidateArrayType(const sptr<AAFwk::IInterface> &value,
const nlohmann::json &propertySchema, const std::string &key);
static bool ValidateObjectType(const sptr<AAFwk::IInterface> &value,
const nlohmann::json &propertySchema, const std::string &key);
static bool ValidateArrayItems(sptr<AAFwk::IArray> arrayObj,
const nlohmann::json &itemsSchema, const std::string &key);
static bool ValidateBasicType(const sptr<AAFwk::IInterface> &value, const std::string &expectedType);
@@ -77,86 +80,22 @@ private:
static bool IsIntegerType(const sptr<AAFwk::IInterface> &value);
static bool IsNumberType(const sptr<AAFwk::IInterface> &value);
static bool IsArrayType(const sptr<AAFwk::IInterface> &value);
static bool IsObjectType(const sptr<AAFwk::IInterface> &value);
// Helper methods for argument mapping
static void ApplyFlagMapping(const std::string &templates, const AAFwk::WantParams &args, std::string &cmdLine);
static void ApplyPositionalMapping(const std::string &order, const AAFwk::WantParams &args, std::string &cmdLine);
static void ApplyFlattenedMapping(const std::string &separator, const std::string &templates,
const AAFwk::WantParams &args, std::string &cmdLine);
static void ApplyJsonStringMapping(const std::string &templates, const AAFwk::WantParams &args,
std::string &cmdLine);
static void ApplyMixedMapping(const std::string &templates, const AAFwk::WantParams &args, std::string &cmdLine);
static std::string FormatTemplate(const std::string &tmpl, const std::string &value);
// Helper methods for args expansion (extracted to reduce nesting depth)
static bool ExpandArgsFromJson(AAFwk::WantParams &args, const std::string &argsStr);
static void ExpandArgsFromWantParams(AAFwk::WantParams &args);
// Helper methods for mode processing (extracted to reduce nesting depth)
static void ProcessPositionalMode(const sptr<AAFwk::IInterface> &value, const nlohmann::json &paramConfig,
std::vector<std::pair<int, std::string>> &positionalParams);
static void ProcessFlattenedMode(const std::string &key, const sptr<AAFwk::IInterface> &value,
const nlohmann::json &paramConfig, const AAFwk::WantParams &args, std::string &cmdLine);
static void ProcessArrayExpansion(const sptr<AAFwk::IInterface> &value, const std::string &tmpl,
static void ProcessBooleanParam(const std::string &key, const sptr<AAFwk::IInterface> &value,
std::string &cmdLine);
static void ProcessJsonStringTemplate(const std::string &key, const sptr<AAFwk::IInterface> &value,
const nlohmann::json &templateValue, std::string &cmdLine);
static void ProcessBooleanTemplate(const std::string &key, const sptr<AAFwk::IInterface> &value,
const nlohmann::json &templateValue, std::string &cmdLine);
static void ProcessFlattenedTemplate(const std::string &flattenedKey, const nlohmann::json &templateValue,
const std::string &separator, const AAFwk::WantParams &args, std::string &cmdLine);
// JSON conversion helper
static std::string ConvertValueToJson(const std::string &key, const sptr<AAFwk::IInterface> &value);
// Nested path query helper for flattened mapping
static sptr<AAFwk::IInterface> QueryNestedValue(const AAFwk::WantParams &args,
const std::string &path, const std::string &separator);
// Helper method for nested path traversal
static sptr<AAFwk::IInterface> QueryNestedPath(const AAFwk::WantParams &args,
const std::vector<std::string> &pathSegments, const std::string &separator);
static sptr<AAFwk::IInterface> QueryNextLevel(const sptr<AAFwk::IInterface> &currentValue,
const std::string &nextSegment, const std::string &separator);
// Helper methods for path query (extracted to reduce QueryNestedValue length)
static sptr<AAFwk::IInterface> TryDirectLookup(const AAFwk::WantParams &args,
const std::string &path);
static sptr<AAFwk::IInterface> TryNestedPathTraversal(const AAFwk::WantParams &args,
const std::string &path, const std::string &separator);
// WantParams to JSON conversion helper for nested objects
static std::string WantParamsToJson(const AAFwk::WantParams &wantParams);
static void ApplyFlattenedModeToSingleParam(const std::string &key, const sptr<AAFwk::IInterface> &value,
const std::string &separator, const nlohmann::json &templateValue, const AAFwk::WantParams &args,
static void ProcessArrayExpansion(const std::string &key, const sptr<AAFwk::IInterface> &value,
std::string &cmdLine);
// Core parameter processing logic (extracted for reuse)
static void ApplyFlagModeLogic(const sptr<AAFwk::IInterface> &value,
const nlohmann::json &templateValue, std::string &cmdLine);
// Type conversion helpers
// GetParamStringValue: only supports basic types (bool, int, long, float, double, string)
// GetParamArrayValue: supports single-level arrays with basic type elements
// GetParamJsonValue: converts to JSON format (supports single-level arrays)
// Note: Nested arrays and byte/char/short types are not supported
static std::string GetParamStringValue(const sptr<AAFwk::IInterface> &value);
static std::string GetParamJsonValue(const sptr<AAFwk::IInterface> &value);
static bool GetParamBoolValue(const sptr<AAFwk::IInterface> &value, bool &result);
static bool GetParamArrayValue(const sptr<AAFwk::IInterface> &value, std::vector<std::string> &result);
// Low-level helper methods for code reuse
static bool ExtractWantParams(const sptr<AAFwk::IInterface> &value, AAFwk::WantParams &wantParams);
static std::string EscapeJsonString(const std::string &str);
static void IterateIArray(sptr<AAFwk::IArray> arrayObj,
std::function<void(const sptr<AAFwk::IInterface>&)> elementHandler);
static std::string BuildJsonArrayFromIArray(sptr<AAFwk::IArray> arrayObj,
std::function<std::string(const sptr<AAFwk::IInterface>&)> elementConverter);
// Type-specific JSON conversion helpers (extracted to reduce GetParamJsonValue length)
static std::string ConvertWantParamsToJson(const sptr<AAFwk::IInterface> &value);
static std::string ConvertArrayToJson(const sptr<AAFwk::IInterface> &value);
static std::string ConvertStringToJson(const sptr<AAFwk::IInterface> &value);
static std::string ConvertBooleanToJson(const sptr<AAFwk::IInterface> &value);
static std::string ConvertNumericToJson(const sptr<AAFwk::IInterface> &value);
};
} // namespace CliTool
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2026 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 "cli_tool_app_state_observer.h"
#include "hilog_tag_wrapper.h"
namespace OHOS {
namespace CliTool {
CliToolAppStateObserver::CliToolAppStateObserver(const std::string &bundleName, ProcessDiedCallback callback)
: bundleName_(bundleName), processDiedCallback_(callback)
{}
void CliToolAppStateObserver::OnProcessDied(const AppExecFwk::ProcessData &processData)
{
TAG_LOGI(AAFwkTag::CLI_TOOL, "Process died: bundleName=%{public}s, pid=%{public}d",
bundleName_.c_str(), processData.pid);
if (processDiedCallback_) {
processDiedCallback_(bundleName_, processData.pid);
}
}
} // namespace CliTool
} // namespace OHOS
@@ -22,18 +22,16 @@
#include <set>
#include <unistd.h>
#include "arg_mapping.h"
#include "cli_error_code.h"
#include "hilog_tag_wrapper.h"
namespace OHOS {
namespace CliTool {
namespace {
constexpr int32_t ERR_OK = 0;
constexpr int32_t ERR_NO_INIT = -1;
constexpr int32_t ERR_FILE_NOT_FOUND = -2;
constexpr int32_t ERR_JSON_PARSE_FAILED = -3;
constexpr int32_t ERR_KVSTORE_NOT_READY = -4;
constexpr int32_t ERR_NAME_NOT_FOUND = -5;
constexpr int32_t CHECK_INTERVAL = 100000; // 100ms
constexpr int32_t MAX_TIMES = 5; // 5 * 100ms = 500ms
@@ -61,6 +59,7 @@ CliToolDataManager::CliToolDataManager()
CliToolDataManager::~CliToolDataManager()
{
TAG_LOGI(AAFwkTag::CLI_TOOL, "CliToolDataManager destructor called");
std::lock_guard<std::mutex> lock(kvStorePtrMutex_);
if (kvStorePtr_ != nullptr) {
dataManager_.CloseKvStore(APP_ID, kvStorePtr_);
}
@@ -296,6 +295,17 @@ int32_t CliToolDataManager::GetAllTools(std::vector<ToolInfo> &tools)
return ERR_OK;
}
int32_t CliToolDataManager::GetAllToolsRawData(ToolsRawData &rawData)
{
std::vector<ToolInfo> tools;
int32_t ret = GetAllTools(tools);
if (ret != ERR_OK) {
return ret;
}
ToolsRawData::FromToolInfoVec(tools, rawData);
return ERR_OK;
}
int32_t CliToolDataManager::GetToolByName(const std::string &name, ToolInfo &tool)
{
TAG_LOGI(AAFwkTag::CLI_TOOL, "GetToolByName called: %{public}s", name.c_str());
@@ -312,7 +322,7 @@ int32_t CliToolDataManager::GetToolByName(const std::string &name, ToolInfo &too
TAG_LOGE(AAFwkTag::SER_ROUTER, "GetToolByName error: %{public}d", status);
if (status == DistributedKv::Status::KEY_NOT_FOUND) {
TAG_LOGW(AAFwkTag::SER_ROUTER, "key not found");
return ERR_NAME_NOT_FOUND;
return ERR_TOOL_NOT_EXIST;
}
RestoreKvStore(status);
return status;
@@ -17,15 +17,18 @@
#include <sys/wait.h>
#include "ability_manager_client.h"
#include "accesstoken_kit.h"
#include "app_mgr_client.h"
#include "ccm_util.h"
#include "cli_error_code.h"
#include "cli_tool_app_state_observer.h"
#include "event_dispatcher.h"
#include "hilog_tag_wrapper.h"
#include "iexec_tool_callback.h"
#include "if_system_ability_manager.h"
#include "ipc_skeleton.h"
#include "iservice_registry.h"
#include "permission_query_util.h"
#include "permission_util.h"
#include "process_manager.h"
#include "session_record.h"
@@ -43,6 +46,9 @@ constexpr int32_t QUERY_SUCCESS = 0;
constexpr int32_t QUERY_COMMAND_NOT_EXIST = 1;
constexpr int32_t QUERY_DB_ERROR = 2;
constexpr int32_t MAX_QUERY_CMDS_SIZE = 100;
constexpr int32_t ACTIVE_TIME = 30 * 1000; // 30s
constexpr int32_t SKILL_TYPE_INDEPENDENT = -1;
sptr<SkillCallbackAdapter> adaptor_;
} // namespace
std::mutex g_mutex;
@@ -61,6 +67,7 @@ sptr<CliToolManagerService> CliToolManagerService::GetInstance()
int32_t CliToolManagerService::RegisterScheduler(const sptr<ICliToolManagerScheduler> &scheduler)
{
InterfaceCallCounter counter(interfaceCalledCount_);
if (EventDispatcher::GetInstance().RegisterScheduler(IPCSkeleton::GetCallingPid(), scheduler)) {
return ERR_OK;
}
@@ -69,6 +76,7 @@ int32_t CliToolManagerService::RegisterScheduler(const sptr<ICliToolManagerSched
int32_t CliToolManagerService::UnregisterScheduler()
{
InterfaceCallCounter counter(interfaceCalledCount_);
EventDispatcher::GetInstance().UnregisterScheduler(IPCSkeleton::GetCallingPid());
return ERR_OK;
}
@@ -81,6 +89,12 @@ void CliToolManagerService::HandleProcessTimeout(const std::string &sessionId)
"HandleProcessTimeout skipped: sessionId=%{public}s not found", sessionId.c_str());
return;
}
if (record->sessionType == SessionType::SKILL) {
HandleSkillSessionTimeout(sessionId);
return;
}
TAG_LOGI(AAFwkTag::CLI_TOOL, "HandleProcessTimeout: sessionId=%{public}s", sessionId.c_str());
record->SetTimedOut(true);
record->SetState(SessionState::CANCELLING);
@@ -96,7 +110,7 @@ void CliToolManagerService::HandleProcessTimeout(const std::string &sessionId)
}
EventDispatcher::GetInstance().DispatchErrorEvent(sessionId, "session timed out");
ProcessManager::GetInstance().TerminateProcess(record->processId, SIGKILL);
ProcessManager::GetInstance().Killpg(record->processId);
}
void CliToolManagerService::HandleProcessYieldTimeout(const std::string &sessionId)
@@ -244,6 +258,8 @@ void CliToolManagerService::OnStart()
TAG_LOGE(AAFwkTag::CLI_TOOL, "Publish failed");
return;
}
DelayUnloadTask();
TAG_LOGI(AAFwkTag::CLI_TOOL, "climgr start success");
}
void CliToolManagerService::OnStop()
@@ -254,7 +270,7 @@ void CliToolManagerService::OnStop()
// Collect active PIDs before clearing sessions
std::vector<pid_t> activePids;
{
std::lock_guard<std::mutex> lock(sessionsMutex_);
std::lock_guard<ffrt::mutex> guard(sessionsMutex_);
for (const auto &[sessionId, record] : sessionRecords_) {
if (record != nullptr && record->processId > 0) {
activePids.push_back(record->processId);
@@ -269,7 +285,7 @@ void CliToolManagerService::OnStop()
// Kill all active processes
auto &processManager = ProcessManager::GetInstance();
for (pid_t pid : activePids) {
processManager.TerminateProcess(pid, SIGKILL);
processManager.Killpg(pid);
}
if (ioMonitor_ != nullptr) {
@@ -277,29 +293,84 @@ void CliToolManagerService::OnStop()
}
}
int32_t CliToolManagerService::OnIdle(const SystemAbilityOnDemandReason &idlReason)
{
int32_t sessionSize = 0;
{
std::lock_guard<ffrt::mutex> guard(sessionsMutex_);
sessionSize = static_cast<int32_t>(sessionRecords_.size());
}
int32_t calledCount = interfaceCalledCount_.load();
if (calledCount != 0 || sessionSize != 0) {
TAG_LOGW(AAFwkTag::CLI_TOOL, "service busy, calledCount=%{public}d, sessionSize=%{public}d",
calledCount, sessionSize);
if (!CancelIdle()) {
TAG_LOGW(AAFwkTag::CLI_TOOL, "Fail to cancel idle");
}
return -1;
}
return 0;
}
void CliToolManagerService::AddSessionRecord(const std::shared_ptr<SessionRecord> &record)
{
std::lock_guard<std::mutex> lock(sessionsMutex_);
std::lock_guard<ffrt::mutex> guard(sessionsMutex_);
sessionRecords_[record->sessionId] = record;
}
std::shared_ptr<SessionRecord> CliToolManagerService::GetSessionRecord(const std::string &sessionId)
{
std::lock_guard<std::mutex> lock(sessionsMutex_);
std::lock_guard<ffrt::mutex> guard(sessionsMutex_);
auto it = sessionRecords_.find(sessionId);
if (it == sessionRecords_.end()) {
TAG_LOGW(AAFwkTag::CLI_TOOL, "GetSessionRecord failed: sessionId=%{public}s not found", sessionId.c_str());
return nullptr;
}
if (it->second == nullptr) {
sessionRecords_.erase(it); // for leak
return nullptr;
}
return it->second;
}
void CliToolManagerService::RemoveSessionRecord(const std::string &sessionId)
{
std::lock_guard<std::mutex> lock(sessionsMutex_);
std::lock_guard<ffrt::mutex> guard(sessionsMutex_);
sessionRecords_.erase(sessionId);
}
void CliToolManagerService::DelayUnloadTask()
{
auto task = []() {
int32_t sessionSize = 0;
{
std::lock_guard<ffrt::mutex> guard(CliToolManagerService::GetInstance()->sessionsMutex_);
sessionSize = static_cast<int32_t>(CliToolManagerService::GetInstance()->sessionRecords_.size());
}
int32_t calledCount = CliToolManagerService::GetInstance()->interfaceCalledCount_.load();
if (calledCount == 0 && sessionSize == 0) {
TAG_LOGI(AAFwkTag::CLI_TOOL, "UnloadSA start");
sptr<ISystemAbilityManager> saManager =
OHOS::SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager();
if (saManager == nullptr) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "null saManager");
return;
}
int32_t result = saManager->UnloadSystemAbility(CLI_TOOL_MGR_SERVICE_ID);
if (result != ERR_OK) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "UnloadSystemAbility ret: %{public}d", result);
return;
}
TAG_LOGI(AAFwkTag::CLI_TOOL, "UnloadSA success");
} else {
TAG_LOGI(AAFwkTag::CLI_TOOL, "Service still busy (calledCount=%{public}d, sessionSize=%{public}d), "
"reschedule delay unload task", calledCount, sessionSize);
CliToolManagerService::GetInstance()->DelayUnloadTask();
}
};
ffrt::submit(std::move(task), ffrt::task_attr().delay(ACTIVE_TIME * COEFFICIENT));
}
bool CliToolManagerService::RegisterSessionWithMonitors(const std::shared_ptr<SessionRecord> &record,
const ExecToolParam &param)
{
@@ -329,10 +400,10 @@ void CliToolManagerService::UnregisterSessionWithMonitors(const std::string &ses
}
}
int32_t CliToolManagerService::GetAllToolInfos(std::vector<ToolInfo> &tools)
int32_t CliToolManagerService::GetAllToolInfos(ToolsRawData &tools)
{
TAG_LOGI(AAFwkTag::CLI_TOOL, "GetAllToolInfos called");
InterfaceCallCounter counter(interfaceCalledCount_);
auto fullTokenId = IPCSkeleton::GetCallingFullTokenID();
if (!AccessToken::TokenIdKit::IsSystemAppByFullTokenID(fullTokenId)) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "GetAllToolInfos: Not system app");
@@ -345,13 +416,13 @@ int32_t CliToolManagerService::GetAllToolInfos(std::vector<ToolInfo> &tools)
return ERR_PERMISSION_DENIED;
}
return CliToolDataManager::GetInstance().GetAllTools(tools);
return CliToolDataManager::GetInstance().GetAllToolsRawData(tools);
}
int32_t CliToolManagerService::GetAllToolSummaries(std::vector<ToolSummary> &summaries)
{
TAG_LOGI(AAFwkTag::CLI_TOOL, "GetAllToolSummaries called");
InterfaceCallCounter counter(interfaceCalledCount_);
auto fullTokenId = IPCSkeleton::GetCallingFullTokenID();
if (!AccessToken::TokenIdKit::IsSystemAppByFullTokenID(fullTokenId)) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "GetAllToolSummaries: Not system app");
@@ -370,7 +441,7 @@ int32_t CliToolManagerService::GetAllToolSummaries(std::vector<ToolSummary> &sum
int32_t CliToolManagerService::GetToolInfoByName(const std::string &name, ToolInfo &tool)
{
TAG_LOGI(AAFwkTag::CLI_TOOL, "GetToolInfoByName called, name='%{public}s'", name.c_str());
InterfaceCallCounter counter(interfaceCalledCount_);
auto fullTokenId = IPCSkeleton::GetCallingFullTokenID();
if (!AccessToken::TokenIdKit::IsSystemAppByFullTokenID(fullTokenId)) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "GetToolInfoByName: Not system app");
@@ -389,14 +460,11 @@ int32_t CliToolManagerService::GetToolInfoByName(const std::string &name, ToolIn
int32_t CliToolManagerService::RegisterTool(const ToolInfo &tool)
{
TAG_LOGI(AAFwkTag::CLI_TOOL, "RegisterTool called, tool name='%{public}s'", tool.name.c_str());
return CliToolDataManager::GetInstance().RegisterTool(tool);
return ERR_PERMISSION_DENIED;
}
int32_t CliToolManagerService::ExecTool(const ExecToolParam &param, const std::string &eventId)
int32_t CliToolManagerService::ValidateExecToolPermissions()
{
TAG_LOGI(AAFwkTag::CLI_TOOL, "ExecTool called: toolName=%{public}s, subcommand=%{public}s",
param.toolName.c_str(), param.subcommand.c_str());
auto fullTokenId = IPCSkeleton::GetCallingFullTokenID();
if (!AccessToken::TokenIdKit::IsSystemAppByFullTokenID(fullTokenId)) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Not system app");
@@ -407,15 +475,24 @@ int32_t CliToolManagerService::ExecTool(const ExecToolParam &param, const std::s
if (!PermissionUtil::VerifyAccessToken(tokenId, PERMISSION_EXEC_CLI_TOOL)) {
return ERR_PERMISSION_DENIED;
}
return ERR_OK;
}
int32_t CliToolManagerService::ValidateSessionLimit()
{
auto cliQuantity = CcmUtil::GetInstance().GetCliConcurrencyLimit();
if (activeSessionCount_.load() >= cliQuantity) {
std::lock_guard<ffrt::mutex> guard(sessionsMutex_);
if (static_cast<int32_t>(sessionRecords_.size()) >= cliQuantity) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Session limit exceeded: %{public}d", cliQuantity);
return ERR_SESSION_LIMIT_EXCEEDED;
}
return ERR_OK;
}
ToolInfo toolInfo;
if (GetToolInfoByName(param.toolName, toolInfo) != ERR_OK) {
int32_t CliToolManagerService::ValidateAndPrepareTool(const ExecToolParam &param, uint32_t tokenId,
ToolInfo &toolInfo, std::string &sandboxConfig, std::string &bundleName)
{
if (CliToolDataManager::GetInstance().GetToolByName(param.toolName, toolInfo) != ERR_OK) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Tool not found");
return ERR_TOOL_NOT_EXIST;
}
@@ -426,45 +503,102 @@ int32_t CliToolManagerService::ExecTool(const ExecToolParam &param, const std::s
return checkPramRet;
}
std::string sandboxConfig;
if (!ToolUtil::GenerateSandboxConfig(param.challenge, tokenId, sandboxConfig)) {
if (!ToolUtil::GenerateSandboxConfig(param, tokenId, sandboxConfig, bundleName)) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "caller is not hap");
return ERR_NOT_HAP;
}
return ERR_OK;
}
// Create and initialize session record
std::shared_ptr<SessionRecord> record = CreateSessionRecord(param);
int32_t CliToolManagerService::SetupAndStartSession(const ExecToolParam &param, const std::string &eventId,
const ToolInfo &toolInfo, const std::string &sandboxConfig, const std::string &bundleName)
{
TAG_LOGI(AAFwkTag::CLI_TOOL,
"Dispatch to CLI path, toolName=%{public}s eventId=%{public}s", param.toolName.c_str(), eventId.c_str());
std::shared_ptr<SessionRecord> record = CreateSessionRecord(param, eventId);
if (record == nullptr) {
return ERR_NO_INIT;
}
record->eventId = eventId;
auto createRet = ProcessManager::GetInstance().CreateChildProcess(param, sandboxConfig, toolInfo, record);
if (createRet != ERR_OK) {
return createRet;
}
activeSessionCount_.fetch_add(1, std::memory_order_relaxed);
AddSessionRecord(record);
if (RegisterSessionWithMonitors(record, param) == false) {
ProcessManager::GetInstance().TerminateProcess(record->processId, SIGKILL);
ProcessManager::GetInstance().Killpg(record->processId);
RemoveSessionRecord(record->sessionId);
return ERR_NO_INIT;
}
// Background session, immediately notify session info
if (param.options.background) {
CliSessionInfo session;
record->BuildSessionInfo(session);
EventDispatcher::GetInstance().DispatchExecToolReplyEvent(record->callerPid, eventId, ERR_OK, session);
if (!bundleName.empty()) {
RegisterAppStateObserver(bundleName, record->callerPid);
}
if (param.options.background) {
HandleBackgroundSessionReply(record, eventId);
}
// Frontground session, after waiting for the yieldMs timeout, notify the session info
return ERR_OK;
}
void CliToolManagerService::PostExecToolTask(int32_t time, const std::string &sessionId, bool isTimeout)
void CliToolManagerService::HandleBackgroundSessionReply(
const std::shared_ptr<SessionRecord> &record, const std::string &eventId)
{
CliSessionInfo session;
record->BuildSessionInfo(session);
EventDispatcher::GetInstance().DispatchExecToolReplyEvent(record->callerPid, eventId, ERR_OK, session);
}
int32_t CliToolManagerService::ExecTool(const ExecToolParam &param, const std::string &eventId)
{
InterfaceCallCounter counter(interfaceCalledCount_);
TAG_LOGI(AAFwkTag::CLI_TOOL, "ExecTool called: toolName=%{public}s, subcommand=%{public}s",
param.toolName.c_str(), param.subcommand.c_str());
ToolInfo toolInfo;
if (ToolUtil::IsSkillTool(param.toolName)) {
int32_t skillType = 0;
auto skillRet = ValidateSkillTypeFromParam(param, skillType);
if (skillRet == ERR_OK && skillType != SKILL_TYPE_INDEPENDENT) {
TAG_LOGI(AAFwkTag::CLI_TOOL,
"Dispatch to skill path, toolName=%{public}s eventId=%{public}s",
param.toolName.c_str(), eventId.c_str());
int32_t ret = SetupAndStartSkillSession(param, eventId, toolInfo);
if (ret != ERR_OK) {
TAG_LOGE(AAFwkTag::CLI_TOOL,
"Skill dispatch failed, toolName=%{public}s ret=%{public}d", param.toolName.c_str(), ret);
}
return ret;
}
if (skillRet != ERR_OK) {
return skillRet;
}
TAG_LOGI(AAFwkTag::CLI_TOOL,
"Independent skill, fallback to CLI path, toolName=%{public}s", param.toolName.c_str());
}
if (auto ret = ValidateExecToolPermissions(); ret != ERR_OK) {
return ret;
}
if (auto ret = ValidateSessionLimit(); ret != ERR_OK) {
return ret;
}
auto tokenId = IPCSkeleton::GetCallingTokenID();
std::string sandboxConfig;
std::string bundleName;
if (auto ret = ValidateAndPrepareTool(param, tokenId, toolInfo, sandboxConfig, bundleName); ret != ERR_OK) {
return ret;
}
return SetupAndStartSession(param, eventId, toolInfo, sandboxConfig, bundleName);
}
void CliToolManagerService::PostExecToolTask(int64_t time, const std::string &sessionId, bool isTimeout)
{
auto timeoutTask = [sessionId, isTimeout]() {
auto service = CliToolManagerService::GetInstance();
@@ -483,15 +617,23 @@ void CliToolManagerService::WaitPid(pid_t pid, int32_t status, int32_t sig)
{
std::shared_ptr<SessionRecord> record = nullptr;
{
std::lock_guard<std::mutex> lock(sessionsMutex_);
for (auto iter = sessionRecords_.begin(); iter != sessionRecords_.end(); ++iter) {
if (iter->second == nullptr || pid != iter->second->processId) {
std::lock_guard<ffrt::mutex> guard(sessionsMutex_);
for (auto iter = sessionRecords_.begin(); iter != sessionRecords_.end();) {
if (iter->second == nullptr) {
std::string sessionId = iter->first;
iter = sessionRecords_.erase(iter);
TAG_LOGW(AAFwkTag::CLI_TOOL, "delete leak sessionId:%{public}s", sessionId.c_str());
continue;
}
record = iter->second;
break;
if (pid == iter->second->processId) {
record = iter->second;
break;
}
++iter;
}
}
AccessToken::AccessTokenKit::DeleteToolTokenByPid(pid);
TAG_LOGI(AAFwkTag::CLI_TOOL, "WaitPid delete tool pid:%{public}d", pid);
if (record) {
record->SetTerminalResult(status, sig);
if (record->OutputDrained()) {
@@ -508,36 +650,105 @@ void CliToolManagerService::sigchld_handler(int32_t sig)
auto instance = CliToolManagerService::GetInstance();
if (instance != nullptr) {
instance->WaitPid(pid, status, sig);
ProcessManager::GetInstance().Killpg(pid);
}
pid_t gPid = getpgid(pid);
TAG_LOGI(AAFwkTag::CLI_TOOL, "gPid=%{public}d", gPid);
if (gPid == -1) {
TAG_LOGI(AAFwkTag::CLI_TOOL, "Fial to get gPid");
return;
}
int32_t killRet = killpg(gPid, SIGTERM);
TAG_LOGI(AAFwkTag::CLI_TOOL, "killpg result:%{public}d", killRet);
}
}
std::shared_ptr<SessionRecord> CliToolManagerService::CreateSessionRecord(const ExecToolParam &param)
void CliToolManagerService::OnProcessDied(const std::string &bundleName, pid_t diedPid)
{
TAG_LOGI(AAFwkTag::CLI_TOOL, "OnProcessDied called: bundleName=%{public}s, diedPid=%{public}d",
bundleName.c_str(), diedPid);
std::lock_guard<ffrt::mutex> guard(sessionsMutex_);
// Iterate through sessionRecords_ to find matching SessionRecord by callerPid
for (auto iter = sessionRecords_.begin(); iter != sessionRecords_.end();) {
auto sessionRecord = iter->second;
if (sessionRecord == nullptr) {
std::string sessionId = iter->first;
iter = sessionRecords_.erase(iter);
TAG_LOGW(AAFwkTag::CLI_TOOL, "delete leak sessionId:%{public}s", sessionId.c_str());
continue;
}
// Check if this session's callerPid matches the diedPid
if (sessionRecord->GetCallerPid() != diedPid) {
++iter;
continue;
}
// Kill the CLI process group (skill sessions have processId=-1, Killpg handles it)
if (sessionRecord->processId > 0) {
ProcessManager::GetInstance().Killpg(sessionRecord->processId);
}
// Clean up session
iter = sessionRecords_.erase(iter);
}
}
void CliToolManagerService::RegisterAppStateObserver(const std::string &bundleName, pid_t callerPid)
{
TAG_LOGI(AAFwkTag::CLI_TOOL, "RegisterAppStateObserver called: bundleName=%{public}s, callerPid=%{public}d",
bundleName.c_str(), callerPid);
// Check if observer already exists for this bundle
if (bundleObservers_.find(bundleName) != bundleObservers_.end()) {
TAG_LOGI(AAFwkTag::CLI_TOOL, "Observer already registered for bundleName=%{public}s", bundleName.c_str());
return;
}
// Create observer with callback to OnProcessDied
auto callback = [](const std::string &bundleName, pid_t diedPid) {
auto service = CliToolManagerService::GetInstance();
if (service != nullptr) {
service->OnProcessDied(bundleName, diedPid);
}
};
sptr<CliToolAppStateObserver> observer = new CliToolAppStateObserver(bundleName, callback);
if (observer == nullptr) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Failed to create observer for bundleName=%{public}s", bundleName.c_str());
return;
}
// Register observer through AppMgrClient
AppExecFwk::AppMgrClient appMgrClient;
auto ret = appMgrClient.ConnectAppMgrService();
if (ret != AppExecFwk::AppMgrResultCode::RESULT_OK) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Failed to connect to AppMgrService");
return;
}
std::vector<std::string> bundleNameList = { bundleName };
auto registerRet = appMgrClient.RegisterApplicationStateObserver(observer, bundleNameList);
if (registerRet != ERR_OK) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Failed to register observer for bundleName=%{public}s, ret=%{public}d",
bundleName.c_str(), registerRet);
return;
}
// Store observer
bundleObservers_[bundleName] = observer;
TAG_LOGI(AAFwkTag::CLI_TOOL, "Successfully registered observer for bundleName=%{public}s", bundleName.c_str());
}
std::shared_ptr<SessionRecord> CliToolManagerService::CreateSessionRecord(const ExecToolParam &param,
const std::string &eventId)
{
auto record = std::make_shared<SessionRecord>();
if (record == nullptr) {
return nullptr;
}
int32_t timeoutMs = param.options.timeout * COEFFICIENT;
record->callerPid = IPCSkeleton::GetCallingPid();
record->sessionId = ToolUtil::GenerateCliSessionId(param.toolName, record);
record->toolName = param.toolName;
record->timeoutMs = timeoutMs;
record->timeoutMs = param.options.timeout * COEFFICIENT;
record->SetState(SessionState::RUNNING);
record->SetBackground(param.options.background);
record->eventId = eventId;
return record;
}
int32_t CliToolManagerService::ClearSession(const std::string &sessionId)
{
InterfaceCallCounter counter(interfaceCalledCount_);
auto fullTokenId = IPCSkeleton::GetCallingFullTokenID();
if (!AccessToken::TokenIdKit::IsSystemAppByFullTokenID(fullTokenId)) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Not system app");
@@ -562,8 +773,18 @@ int32_t CliToolManagerService::ClearSession(const std::string &sessionId)
}
TAG_LOGI(AAFwkTag::CLI_TOOL, "ClearSession: sessionId=%{public}s, pid=%{public}d",
sessionId.c_str(), record->processId);
if (!ProcessManager::GetInstance().TerminateProcess(record->processId, SIGTERM)) {
return ERR_PERMISSION_DENIED;
if (record->sessionType == SessionType::SKILL) {
// Skill sessions don't have a child process, mark as cancelling and clean up
record->SetState(SessionState::CANCELLING);
EventDispatcher::GetInstance().DispatchExitEvent(sessionId, 0);
EventDispatcher::GetInstance().ClearSessionSubscribers(sessionId);
RemoveSessionRecord(sessionId);
return ERR_OK;
}
if (!ProcessManager::GetInstance().Killpg(record->processId)) {
return ERR_NOT_KILL;
}
record->SetState(SessionState::CANCELLING);
return ERR_OK;
@@ -571,6 +792,7 @@ int32_t CliToolManagerService::ClearSession(const std::string &sessionId)
int32_t CliToolManagerService::SubscribeSession(const std::string &sessionId, const std::string &subscriptionId)
{
InterfaceCallCounter counter(interfaceCalledCount_);
auto fullTokenId = IPCSkeleton::GetCallingFullTokenID();
if (!AccessToken::TokenIdKit::IsSystemAppByFullTokenID(fullTokenId)) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Not system app");
@@ -588,12 +810,21 @@ int32_t CliToolManagerService::SubscribeSession(const std::string &sessionId, co
sessionId.c_str(), subscriptionId.c_str());
return ERR_INVALID_PARAM;
}
if (GetSessionRecord(sessionId) == nullptr) {
auto record = GetSessionRecord(sessionId);
if (record == nullptr) {
TAG_LOGE(AAFwkTag::CLI_TOOL,
"SubscribeSession failed: sessionId=%{public}s not found, subscriptionId=%{public}s",
sessionId.c_str(), subscriptionId.c_str());
return ERR_CLI_SESSION_NOT_FOUND;
}
CliSessionInfo session;
record->BuildSessionInfo(session);
if (session.status != "running") {
TAG_LOGE(AAFwkTag::CLI_TOOL,
"SubscribeSession failed: sessionId=%{public}s status=%{public}s is not subscribable",
sessionId.c_str(), session.status.c_str());
return ERR_CLI_SESSION_NOT_FOUND;
}
if (!EventDispatcher::GetInstance().RegisterSubscriber(
sessionId, subscriptionId, IPCSkeleton::GetCallingPid())) {
return ERR_NO_INIT;
@@ -603,6 +834,7 @@ int32_t CliToolManagerService::SubscribeSession(const std::string &sessionId, co
int32_t CliToolManagerService::UnsubscribeSession(const std::string &sessionId, const std::string &subscriptionId)
{
InterfaceCallCounter counter(interfaceCalledCount_);
auto fullTokenId = IPCSkeleton::GetCallingFullTokenID();
if (!AccessToken::TokenIdKit::IsSystemAppByFullTokenID(fullTokenId)) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Not system app");
@@ -623,6 +855,7 @@ int32_t CliToolManagerService::UnsubscribeSession(const std::string &sessionId,
int32_t CliToolManagerService::QuerySession(const std::string &sessionId, CliSessionInfo &session)
{
InterfaceCallCounter counter(interfaceCalledCount_);
auto fullTokenId = IPCSkeleton::GetCallingFullTokenID();
if (!AccessToken::TokenIdKit::IsSystemAppByFullTokenID(fullTokenId)) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Not system app");
@@ -646,6 +879,7 @@ int32_t CliToolManagerService::QuerySession(const std::string &sessionId, CliSes
int32_t CliToolManagerService::SendMessage(const std::string &sessionId,
const std::string &inputText, const std::string &eventId)
{
InterfaceCallCounter counter(interfaceCalledCount_);
auto fullTokenId = IPCSkeleton::GetCallingFullTokenID();
if (!AccessToken::TokenIdKit::IsSystemAppByFullTokenID(fullTokenId)) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Not system app");
@@ -674,115 +908,206 @@ int32_t CliToolManagerService::SendMessage(const std::string &sessionId,
return ERR_CLI_SEND_MESSAGE;
}
// Skill sessions don't support stdin
if (record->sessionType == SessionType::SKILL) {
TAG_LOGE(AAFwkTag::CLI_TOOL,
"SendMessage failed: sessionId=%{public}s is a skill session (no stdin)", sessionId.c_str());
return ERR_CLI_SEND_MESSAGE;
}
ioMonitor_->SendMessage(sessionId, inputText, eventId);
return ERR_OK;
}
int32_t CliToolManagerService::BatchQueryPermissionBySubCommand(const std::vector<Command> &cmds,
int32_t CliToolManagerService::BatchQueryPermissionBySubCommand(
const std::vector<Command> &cmds,
std::vector<CommandPermission> &cmdPermissions)
{
TAG_LOGI(AAFwkTag::CLI_TOOL, "BatchQueryPermissionBySubCommand called, count=%{public}zu", cmds.size());
TAG_LOGI(AAFwkTag::CLI_TOOL, "BatchQueryPermissionBySubCommand begin, cnt=%{public}zu", cmds.size());
InterfaceCallCounter counter(interfaceCalledCount_);
if (cmds.empty() || cmds.size() >= MAX_QUERY_CMDS_SIZE) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Commands is empty or reach limit");
TAG_LOGE(AAFwkTag::CLI_TOOL, "cmds is empty or reach limit");
return ERR_INVALID_PARAM;
}
Security::AccessToken::AccessTokenID callerToken = IPCSkeleton::GetCallingTokenID();
Security::AccessToken::ATokenTypeEnum tokenType =
Security::AccessToken::AccessTokenKit::GetTokenTypeFlag(callerToken);
if (tokenType != Security::AccessToken::ATokenTypeEnum::TOKEN_NATIVE) {
auto callerToken = IPCSkeleton::GetCallingTokenID();
if (Security::AccessToken::AccessTokenKit::GetTokenTypeFlag(callerToken) !=
Security::AccessToken::ATokenTypeEnum::TOKEN_NATIVE) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Caller is not SA");
return ERR_NOT_SA_CALLER;
}
int ret = Security::AccessToken::AccessTokenKit::VerifyAccessToken(callerToken, "ohos.permission.QUERY_CLI_TOOL");
if (ret != Security::AccessToken::PermissionState::PERMISSION_GRANTED) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Permission denied: ohos.permission.QUERY_CLI_TOOL");
if (!PermissionUtil::VerifyAccessToken(callerToken, PERMISSION_QUERY_CLI_TOOL)) {
return ERR_PERMISSION_DENIED;
}
TAG_LOGI(AAFwkTag::CLI_TOOL, "BatchQueryPermissionBySubCommand begin");
cmdPermissions.clear();
cmdPermissions.reserve(cmds.size());
return PermissionQueryUtil::BatchQueryPermissions(cmds, cmdPermissions);
}
for (const auto &cmd : cmds) {
CommandPermission cmdPerm;
cmdPerm.cmd = cmd;
cmdPerm.permissions.clear();
if (cmd.toolName.empty()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Tool name is empty");
return ERR_INVALID_PARAM;
}
SkillCallbackAdapter::SkillCallbackAdapter(const std::string &sessionId,
int32_t callerPid, const std::string &eventId)
: sessionId_(sessionId), callerPid_(callerPid), eventId_(eventId)
{}
int32_t ret = DoQueryPermission(cmd, cmdPerm.permissions);
if (ret == ERR_OK) {
cmdPerm.queryRet = QUERY_SUCCESS;
} else if (ret == ERR_TOOL_NOT_EXIST) {
cmdPerm.queryRet = QUERY_COMMAND_NOT_EXIST;
cmdPerm.permissions.clear();
} else {
// ERR_NO_INIT
cmdPerm.queryRet = QUERY_DB_ERROR;
cmdPerm.permissions.clear();
}
cmdPermissions.push_back(std::move(cmdPerm));
void SkillCallbackAdapter::OnExecuteDone(const std::string &requestCode, int32_t resultCode,
const AppExecFwk::SkillExecuteResult &result)
{
TAG_LOGI(AAFwkTag::CLI_TOOL,
"SkillCallbackAdapter::OnExecuteDone sessionId:%{public}s code:%{public}d",
sessionId_.c_str(), resultCode);
auto service = CliToolManagerService::GetInstance();
if (service == nullptr) {
TAG_LOGW(AAFwkTag::CLI_TOOL, "service expired for sessionId:%{public}s", sessionId_.c_str());
return;
}
TAG_LOGI(AAFwkTag::CLI_TOOL, "Batch query completed, total=%{public}zu", cmdPermissions.size());
auto record = service->GetSessionRecord(sessionId_);
if (record == nullptr) {
TAG_LOGW(AAFwkTag::CLI_TOOL,
"OnExecuteDone skipped: sessionId:%{public}s already cleaned", sessionId_.c_str());
return;
}
std::string outputText;
if (result.result != nullptr) {
outputText = result.result->ToString();
}
record->SetSkillResult(resultCode, outputText);
record->SetState(resultCode == ERR_OK ? SessionState::COMPLETED : SessionState::FAILED);
auto session = ToolUtil::BuildSkillSessionInfo(sessionId_, resultCode, result);
service->HandleSkillSessionComplete(sessionId_, callerPid_, eventId_, resultCode, session);
}
int32_t CliToolManagerService::ValidateSkillTypeFromParam(const ExecToolParam &param, int32_t &skillType)
{
auto &args = const_cast<ExecToolParam &>(param).args;
ToolUtil::NormalizeSkillParamKeys(args);
auto bundleName = args.GetStringParam("bundleName");
auto moduleName = args.GetStringParam("moduleName");
auto skillName = args.GetStringParam("skillName");
if (skillName.empty()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "skillName is required in args");
return ERR_INVALID_VALUE;
}
return ValidateSkillType(bundleName, moduleName, skillName, skillType);
}
int32_t CliToolManagerService::ValidateSkillType(const std::string &bundleName,
const std::string &moduleName, const std::string &skillName, int32_t &skillType)
{
auto queryRet = AAFwk::AbilityManagerClient::GetInstance()->QuerySkillType(
bundleName, moduleName, skillName, skillType);
if (queryRet != ERR_OK) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "querySkillType failed:%{public}d", queryRet);
return queryRet;
}
return ERR_OK;
}
int32_t CliToolManagerService::DoQueryPermission(const Command &cmd, std::vector<std::string> &permissions)
int32_t CliToolManagerService::SetupAndStartSkillSession(const ExecToolParam &param,
const std::string &eventId, const ToolInfo &toolInfo)
{
if (cmd.subCommand.empty()) {
return QueryMainCommandPermission(cmd.toolName, permissions);
}
return QuerySubCommandPermission(cmd.toolName, cmd.subCommand, permissions);
}
TAG_LOGI(AAFwkTag::CLI_TOOL, "SetupAndStartSkillSession: toolName=%{public}s",
param.toolName.c_str());
int32_t CliToolManagerService::QueryMainCommandPermission(const std::string &toolName,
std::vector<std::string> &permissions)
{
ToolInfo toolInfo;
int32_t ret = CliToolDataManager::GetInstance().GetToolByName(toolName, toolInfo);
if (ret == ERR_NO_INIT) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "DB error when querying tool: %{public}s", toolName.c_str());
auto &args = const_cast<ExecToolParam &>(param).args;
auto bundleName = args.GetStringParam("bundleName");
auto moduleName = args.GetStringParam("moduleName");
auto skillName = args.GetStringParam("skillName");
auto scriptPath = args.GetStringParam("scriptPath");
auto funcName = args.GetStringParam("functionName");
ToolUtil::ExpandArgsJsonString(args);
auto skillArgs = ToolUtil::FilterSkillArgs(args);
auto record = CreateSessionRecord(param, eventId);
if (record == nullptr) {
return ERR_NO_INIT;
} else if (ret != ERR_OK) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Tool not found: %{public}s", toolName.c_str());
return ERR_TOOL_NOT_EXIST;
}
permissions = toolInfo.requirePermissions;
record->sessionType = SessionType::SKILL;
AddSessionRecord(record);
auto callerTokenId = IPCSkeleton::GetCallingTokenID();
adaptor_ = sptr<SkillCallbackAdapter>::MakeSptr(
record->sessionId, record->callerPid, eventId);
AppExecFwk::SkillExecuteRequest skillRequest;
skillRequest.callerTokenId = callerTokenId;
skillRequest.bundleName = bundleName;
skillRequest.moduleName = moduleName;
skillRequest.skillName = skillName;
skillRequest.scriptPath = scriptPath;
skillRequest.functionName = funcName;
skillRequest.skillArgs = skillArgs;
TAG_LOGD(AAFwkTag::CLI_TOOL, "execSkill before ExecuteInAppSkillWithTokenId");
int32_t ret = AAFwk::AbilityManagerClient::GetInstance()->ExecuteInAppSkillWithTokenId(
skillRequest, adaptor_);
if (ret != ERR_OK) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "ExecuteInAppSkillWithTokenId failed:%{public}d", ret);
RemoveSessionRecord(record->sessionId);
return ret;
}
if (param.options.background) {
HandleBackgroundSessionReply(record, eventId);
}
return ERR_OK;
}
int32_t CliToolManagerService::QuerySubCommandPermission(const std::string &toolName, const std::string &subCommand,
std::vector<std::string> &permissions)
void CliToolManagerService::HandleSkillSessionComplete(const std::string &sessionId,
int32_t callerPid, const std::string &eventId, int32_t resultCode,
const CliSessionInfo &session)
{
ToolInfo toolInfo;
int32_t ret = CliToolDataManager::GetInstance().GetToolByName(toolName, toolInfo);
if (ret == ERR_NO_INIT) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "DB error when querying tool: %{public}s", toolName.c_str());
return ERR_NO_INIT;
} else if (ret != ERR_OK) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Tool not found: %{public}s", toolName.c_str());
return ERR_TOOL_NOT_EXIST;
auto record = GetSessionRecord(sessionId);
if (record == nullptr) {
TAG_LOGW(AAFwkTag::CLI_TOOL,
"HandleSkillSessionComplete skipped: sessionId:%{public}s not found", sessionId.c_str());
return;
}
if (!record->BeginCleanup()) {
TAG_LOGW(AAFwkTag::CLI_TOOL,
"HandleSkillSessionComplete skipped: already cleaning sessionId:%{public}s", sessionId.c_str());
return;
}
if (!toolInfo.hasSubCommand) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Tool has no subcommand: %{public}s", toolName.c_str());
return ERR_TOOL_NOT_EXIST;
auto oldBackground = record->SetBackground(true);
if (oldBackground == false) {
EventDispatcher::GetInstance().DispatchExecToolReplyEvent(callerPid, eventId, ERR_OK, session);
}
auto it = toolInfo.subcommands.find(subCommand);
if (it == toolInfo.subcommands.end()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Subcommand not found: %{public}s.%{public}s",
toolName.c_str(), subCommand.c_str());
return ERR_TOOL_NOT_EXIST;
EventDispatcher::GetInstance().DispatchExitEvent(sessionId, 0);
EventDispatcher::GetInstance().ClearSessionSubscribers(sessionId);
RemoveSessionRecord(sessionId);
}
void CliToolManagerService::HandleSkillSessionTimeout(const std::string &sessionId)
{
auto record = GetSessionRecord(sessionId);
if (record == nullptr) {
TAG_LOGW(AAFwkTag::CLI_TOOL,
"HandleSkillSessionTimeout skipped: sessionId:%{public}s not found", sessionId.c_str());
return;
}
permissions = it->second.requirePermissions;
return ERR_OK;
record->SetTimedOut(true);
record->SetState(SessionState::FAILED);
auto oldBackground = record->SetBackground(true);
if (oldBackground == false) {
CliSessionInfo session;
record->BuildSessionInfo(session);
EventDispatcher::GetInstance().DispatchExecToolReplyEvent(
record->callerPid, record->eventId, ERR_OK, session);
}
EventDispatcher::GetInstance().DispatchErrorEvent(sessionId, "session timed out");
EventDispatcher::GetInstance().DispatchExitEvent(sessionId, 0);
EventDispatcher::GetInstance().ClearSessionSubscribers(sessionId);
RemoveSessionRecord(sessionId);
}
} // namespace CliTool
} // namespace OHOS
@@ -16,6 +16,7 @@
#include "event_dispatcher.h"
#include <chrono>
#include <vector>
#include "hilog_tag_wrapper.h"
@@ -318,4 +319,4 @@ void EventDispatcher::RemoveSubscribersForPidLocked(int32_t callerPid)
}
} // namespace CliTool
} // namespace OHOS
} // namespace OHOS
@@ -15,11 +15,15 @@
#include "io_monitor.h"
#include <cstring>
#include <algorithm>
#include <cerrno>
#include <chrono>
#include <cstring>
#include <fcntl.h>
#include <poll.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <utility>
#include <vector>
#include "ffrt.h"
@@ -33,8 +37,10 @@ namespace CliTool {
namespace {
constexpr int32_t MAX_EVENTS = 16;
constexpr int32_t EPOLL_WAIT_MS = 100;
constexpr int32_t MAX_RETRIES = 10;
constexpr int32_t RETRY_DELAY_MS = 10;
constexpr int32_t INPUT_WRITE_POLL_MS = 1000;
constexpr int32_t INPUT_WRITE_TIMEOUT_MS = 30 * 1000;
constexpr size_t MAX_PENDING_INPUT_BYTES = 4 * 1024 * 1024;
constexpr size_t MAX_PENDING_INPUT_MESSAGES = 4096;
}
std::shared_ptr<IOMonitor> IOMonitor::Create()
@@ -75,11 +81,27 @@ void IOMonitor::Stop()
monitorThread_.join();
}
std::lock_guard<std::mutex> lock(fdMutex_);
for (const auto &[fd, info] : fdMap_) {
close(fd);
std::vector<std::pair<std::string, PendingInput>> failedInputs;
{
std::lock_guard<std::mutex> lock(fdMutex_);
for (const auto &[fd, info] : fdMap_) {
close(fd);
}
fdMap_.clear();
for (auto &[sessionId, queue] : inputQueues_) {
while (!queue.pendingInputs.empty()) {
failedInputs.emplace_back(sessionId, std::move(queue.pendingInputs.front()));
queue.pendingInputs.pop_front();
}
queue.pendingBytes = 0;
queue.writeTaskRunning = false;
}
inputQueues_.clear();
}
for (const auto &[sessionId, input] : failedInputs) {
NotifyInputReply(sessionId, input.eventId, false);
}
fdMap_.clear();
}
bool IOMonitor::RegisterSession(const std::string &sessionId, int stdoutFd, int stderrFd, int stdinFd)
@@ -129,6 +151,7 @@ bool IOMonitor::RegisterSession(const std::string &sessionId, int stdoutFd, int
void IOMonitor::UnregisterSession(const std::string &sessionId)
{
std::vector<std::pair<int, FdInfo>> fdsToClose;
std::vector<PendingInput> failedInputs;
{
std::lock_guard<std::mutex> lock(fdMutex_);
for (auto it = fdMap_.begin(); it != fdMap_.end();) {
@@ -139,6 +162,14 @@ void IOMonitor::UnregisterSession(const std::string &sessionId)
}
++it;
}
auto queueIt = inputQueues_.find(sessionId);
if (queueIt != inputQueues_.end()) {
while (!queueIt->second.pendingInputs.empty()) {
failedInputs.emplace_back(std::move(queueIt->second.pendingInputs.front()));
queueIt->second.pendingInputs.pop_front();
}
inputQueues_.erase(queueIt);
}
}
for (const auto &[fd, info] : fdsToClose) {
@@ -147,6 +178,9 @@ void IOMonitor::UnregisterSession(const std::string &sessionId)
}
close(fd);
}
for (const auto &input : failedInputs) {
NotifyInputReply(sessionId, input.eventId, false);
}
}
void IOMonitor::SetOutputCallback(OutputCallback callback)
@@ -172,6 +206,11 @@ void IOMonitor::SetSessionDrainedCallback(SessionDrainedCallback callback)
int IOMonitor::GetStdinFd(const std::string &sessionId)
{
std::lock_guard<std::mutex> lock(fdMutex_);
return GetStdinFdLocked(sessionId);
}
int IOMonitor::GetStdinFdLocked(const std::string &sessionId) const
{
auto it = fdMap_.begin();
while (it != fdMap_.end()) {
if (it->second.sessionId == sessionId && it->second.isStdin) {
@@ -188,52 +227,77 @@ int IOMonitor::GetStdinFd(const std::string &sessionId)
return it->first;
}
void IOMonitor::WriteTask(const std::string &sessionId, const std::string &message, const std::string &eventId)
bool IOMonitor::WriteMessage(int fd, const std::string &sessionId, const std::string &message)
{
int fd = GetStdinFd(sessionId);
if (fd < 0) {
if (inputReplyCallback_) {
inputReplyCallback_(sessionId, eventId, false);
}
return;
if (message.empty()) {
return true;
}
bool result = true;
const char* data = message.c_str();
size_t totalBytes = message.size();
size_t bytesWritten = 0;
int retryCount = 0;
while (bytesWritten < totalBytes && retryCount < MAX_RETRIES) {
auto beginTime = std::chrono::steady_clock::now();
while (bytesWritten < totalBytes) {
ssize_t writeResult = write(fd, data + bytesWritten, totalBytes - bytesWritten);
if (writeResult == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
std::this_thread::sleep_for(std::chrono::milliseconds(RETRY_DELAY_MS));
retryCount++;
if (errno == EINTR) {
continue;
} else {
TAG_LOGE(AAFwkTag::CLI_TOOL,
"WriteTask failed: write error=%{public}s for sessionId=%{public}s",
strerror(errno), sessionId.c_str());
result = false;
break;
}
} else if (writeResult == 0) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
TAG_LOGE(AAFwkTag::CLI_TOOL,
"WriteMessage failed: write error=%{public}s for sessionId=%{public}s",
strerror(errno), sessionId.c_str());
return false;
}
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - beginTime).count();
if (elapsed >= INPUT_WRITE_TIMEOUT_MS) {
TAG_LOGE(AAFwkTag::CLI_TOOL,
"WriteMessage failed: wait writable timeout for sessionId=%{public}s, "
"wrote=%{public}zu/%{public}zu",
sessionId.c_str(), bytesWritten, totalBytes);
return false;
}
pollfd pollFd {};
pollFd.fd = fd;
pollFd.events = POLLOUT;
int32_t pollTimeout = std::min<int64_t>(INPUT_WRITE_POLL_MS, INPUT_WRITE_TIMEOUT_MS - elapsed);
int32_t pollResult = poll(&pollFd, 1, pollTimeout);
if (pollResult < 0) {
if (errno == EINTR) {
continue;
}
TAG_LOGE(AAFwkTag::CLI_TOOL,
"WriteMessage failed: poll error=%{public}s for sessionId=%{public}s",
strerror(errno), sessionId.c_str());
return false;
}
if (pollResult == 0) {
continue;
}
if ((pollFd.revents & (POLLERR | POLLHUP | POLLNVAL)) != 0) {
TAG_LOGE(AAFwkTag::CLI_TOOL,
"WriteMessage failed: poll revents=%{public}d for sessionId=%{public}s",
pollFd.revents, sessionId.c_str());
return false;
}
continue;
}
if (writeResult == 0) {
TAG_LOGE(AAFwkTag::CLI_TOOL,
"WriteTask failed: pipe closed for sessionId=%{public}s",
"WriteMessage failed: pipe closed for sessionId=%{public}s",
sessionId.c_str());
result = false;
break;
return false;
}
bytesWritten += writeResult;
retryCount = 0;
}
if (bytesWritten < totalBytes) {
result = false;
}
if (result == false) {
TAG_LOGW(AAFwkTag::CLI_TOOL, "WriteTask: partial write for sessionId=%{public}s, "
"wrote=%{public}zu/%{public}zu", sessionId.c_str(), bytesWritten, totalBytes);
}
return true;
}
void IOMonitor::NotifyInputReply(const std::string &sessionId, const std::string &eventId, bool result)
{
if (inputReplyCallback_) {
inputReplyCallback_(sessionId, eventId, result);
}
@@ -241,15 +305,97 @@ void IOMonitor::WriteTask(const std::string &sessionId, const std::string &messa
void IOMonitor::SendMessage(const std::string &sessionId, const std::string &message, const std::string &eventId)
{
auto writeTask = [weak = weak_from_this(), sessionId, message, eventId]() {
bool shouldSubmit = false;
bool rejected = false;
{
std::lock_guard<std::mutex> lock(fdMutex_);
if (GetStdinFdLocked(sessionId) < 0) {
rejected = true;
} else {
auto &queue = inputQueues_[sessionId];
if (queue.pendingBytes + message.size() > MAX_PENDING_INPUT_BYTES ||
queue.pendingInputs.size() >= MAX_PENDING_INPUT_MESSAGES) {
TAG_LOGW(AAFwkTag::CLI_TOOL,
"SendMessage failed: input queue full for sessionId=%{public}s, pendingBytes=%{public}zu, "
"pendingMessages=%{public}zu",
sessionId.c_str(), queue.pendingBytes, queue.pendingInputs.size());
rejected = true;
} else {
queue.pendingInputs.emplace_back(PendingInput {message, eventId});
queue.pendingBytes += message.size();
if (!queue.writeTaskRunning) {
queue.writeTaskRunning = true;
shouldSubmit = true;
}
}
}
}
if (rejected) {
NotifyInputReply(sessionId, eventId, false);
return;
}
if (!shouldSubmit) {
return;
}
auto writeTask = [weak = weak_from_this(), sessionId]() {
auto sharedThis = weak.lock();
if (sharedThis) {
sharedThis->WriteTask(sessionId, message, eventId);
sharedThis->ProcessWriteQueue(sessionId);
}
};
ffrt::submit(std::move(writeTask));
}
void IOMonitor::ProcessWriteQueue(const std::string &sessionId)
{
while (true) {
PendingInput input;
int fd = -1;
{
std::lock_guard<std::mutex> lock(fdMutex_);
auto queueIt = inputQueues_.find(sessionId);
if (queueIt == inputQueues_.end() || queueIt->second.pendingInputs.empty()) {
if (queueIt != inputQueues_.end()) {
queueIt->second.writeTaskRunning = false;
if (queueIt->second.pendingBytes == 0) {
inputQueues_.erase(queueIt);
}
}
return;
}
input = std::move(queueIt->second.pendingInputs.front());
queueIt->second.pendingInputs.pop_front();
queueIt->second.pendingBytes -= input.message.size();
fd = GetStdinFdLocked(sessionId);
}
bool result = fd >= 0 && WriteMessage(fd, sessionId, input.message);
NotifyInputReply(sessionId, input.eventId, result);
if (!result) {
std::vector<PendingInput> failedInputs;
{
std::lock_guard<std::mutex> lock(fdMutex_);
auto queueIt = inputQueues_.find(sessionId);
if (queueIt != inputQueues_.end()) {
while (!queueIt->second.pendingInputs.empty()) {
failedInputs.emplace_back(std::move(queueIt->second.pendingInputs.front()));
queueIt->second.pendingInputs.pop_front();
}
inputQueues_.erase(queueIt);
}
}
for (const auto &failedInput : failedInputs) {
NotifyInputReply(sessionId, failedInput.eventId, false);
}
return;
}
}
}
void IOMonitor::MonitorLoop()
{
epoll_event events[MAX_EVENTS];
@@ -0,0 +1,118 @@
/*
* Copyright (c) 2026 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 "permission_query_util.h"
#include "cli_tool_data_manager.h"
#include "hilog_tag_wrapper.h"
namespace OHOS {
namespace CliTool {
int32_t PermissionQueryUtil::BatchQueryPermissions(
const std::vector<Command> &cmds,
std::vector<CommandPermission> &cmdPermissions)
{
cmdPermissions.clear();
cmdPermissions.reserve(cmds.size());
for (const auto &cmd : cmds) {
std::vector<std::string> permissions;
int32_t ret = QuerySingleCommand(cmd, permissions);
int32_t queryRet;
if (ret == ERR_OK) {
queryRet = QueryResult::SUCCESS;
} else if (ret == ERR_TOOL_NOT_EXIST) {
queryRet = QueryResult::COMMAND_NOT_EXIST;
permissions.clear();
} else {
queryRet = QueryResult::DB_ERROR;
permissions.clear();
}
cmdPermissions.push_back(BuildCommandPermission(cmd, permissions, queryRet));
}
TAG_LOGI(AAFwkTag::CLI_TOOL, "Batch query completed, total=%{public}zu", cmdPermissions.size());
return ERR_OK;
}
int32_t PermissionQueryUtil::QuerySingleCommand(
const Command &cmd,
std::vector<std::string> &permissions)
{
if (cmd.toolName.empty()) {
TAG_LOGW(AAFwkTag::CLI_TOOL, "Tool name is empty");
return ERR_TOOL_NOT_EXIST;
}
if (cmd.subCommand.empty()) {
return QueryMainCommandPermission(cmd.toolName, permissions);
}
return QuerySubCommandPermission(cmd.toolName, cmd.subCommand, permissions);
}
int32_t PermissionQueryUtil::QueryMainCommandPermission(
const std::string &toolName,
std::vector<std::string> &permissions)
{
ToolInfo toolInfo;
int32_t ret = CliToolDataManager::GetInstance().GetToolByName(toolName, toolInfo);
if (ret == ERR_NO_INIT) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "DB error when querying tool: %{public}s", toolName.c_str());
return ERR_NO_INIT;
} else if (ret != ERR_OK) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Tool not found: %{public}s", toolName.c_str());
return ERR_TOOL_NOT_EXIST;
}
permissions = toolInfo.requirePermissions;
return ERR_OK;
}
int32_t PermissionQueryUtil::QuerySubCommandPermission(
const std::string &toolName,
const std::string &subCommand,
std::vector<std::string> &permissions)
{
ToolInfo toolInfo;
int32_t ret = CliToolDataManager::GetInstance().GetToolByName(toolName, toolInfo);
if (ret == ERR_NO_INIT) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "DB error when querying tool: %{public}s", toolName.c_str());
return ERR_NO_INIT;
} else if (ret != ERR_OK) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Tool not found: %{public}s", toolName.c_str());
return ERR_TOOL_NOT_EXIST;
}
if (!toolInfo.hasSubCommand) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Tool has no subcommand: %{public}s", toolName.c_str());
return ERR_TOOL_NOT_EXIST;
}
auto it = toolInfo.subcommands.find(subCommand);
if (it == toolInfo.subcommands.end()) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "Subcommand not found: %{public}s.%{public}s",
toolName.c_str(), subCommand.c_str());
return ERR_TOOL_NOT_EXIST;
}
permissions = it->second.requirePermissions;
return ERR_OK;
}
CommandPermission PermissionQueryUtil::BuildCommandPermission(
const Command &cmd,
const std::vector<std::string> &permissions,
int32_t queryRet)
{
CommandPermission cmdPerm;
cmdPerm.cmd = cmd;
cmdPerm.permissions = permissions;
cmdPerm.queryRet = queryRet;
return cmdPerm;
}
} // namespace CliTool
} // namespace OHOS
@@ -128,7 +128,8 @@ int32_t ProcessManager::CreateChildProcess(const ExecToolParam &param, const std
execArgs.push_back(nullptr);
TAG_LOGI(AAFwkTag::CLI_TOOL, "Before execvp");
execvp(execArgs[0], execArgs.data());
_exit(0);
TAG_LOGE(AAFwkTag::CLI_TOOL, "execvp failed:%{public}d", errno);
_exit(EXIT_FAILURE);
}
// Parent process: close write ends of pipes
@@ -141,11 +142,11 @@ int32_t ProcessManager::CreateChildProcess(const ExecToolParam &param, const std
return ERR_OK;
}
bool ProcessManager::TerminateProcess(pid_t pid, int signal) const
bool ProcessManager::Killpg(pid_t pid) const
{
if (pid > 0 && kill(pid, signal) != 0 && errno != ESRCH) {
TAG_LOGW(AAFwkTag::CLI_TOOL, "Failed to kill process %{public}d: %{public}s",
pid, strerror(errno));
int32_t killRet = kill(0 - pid, SIGTERM);
if (killRet != 0) {
TAG_LOGW(AAFwkTag::CLI_TOOL, "killpg result:%{public}d", killRet);
return false;
}
return true;
@@ -28,7 +28,7 @@ SessionState SessionRecord::GetState() const
return state_.load(std::memory_order_acquire);
}
void SessionRecord::SetTerminalResult(int32_t status, int32_t sig)
void SessionRecord::SetTerminalResult(int32_t status, int32_t sig) // for waitpid
{
std::lock_guard<std::mutex> lock(resultMutex_);
terminalStatus_ = status;
@@ -38,6 +38,18 @@ void SessionRecord::SetTerminalResult(int32_t status, int32_t sig)
processExited_.store(true, std::memory_order_release);
}
void SessionRecord::SetSkillResult(int32_t resultCode, const std::string &outputText)
{
std::lock_guard<std::mutex> lock(resultMutex_);
terminalStatus_ = resultCode;
stdoutText_ = outputText;
endTimeMs_ = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
processExited_.store(true, std::memory_order_release);
stdoutClosed_.store(true, std::memory_order_release);
stderrClosed_.store(true, std::memory_order_release);
}
int32_t SessionRecord::GetTerminalStatus() const
{
std::lock_guard<std::mutex> lock(resultMutex_);
@@ -120,14 +132,12 @@ void SessionRecord::BuildSessionInfo(CliSessionInfo &session) const
session.sessionId = sessionId;
session.toolName = toolName;
if (!HasProcessExited() || !OutputDrained()) {
session.result = nullptr;
if ((!HasProcessExited() || !OutputDrained()) && !timedOut_) {
session.status = "running";
} else {
session.result = BuildExecResult();
session.status =
(!session.result || session.result->timedOut || session.result->exitCode != 0) ?
"failed" : "completed";
(!session.result || session.result->timedOut || session.result->exitCode != 0) ? "failed" : "completed";
}
}
@@ -142,17 +152,17 @@ void SessionRecord::TrimBufferedOutput(std::string &buffer)
std::shared_ptr<ExecResult> SessionRecord::BuildExecResult() const
{
auto result = std::make_shared<ExecResult>();
if (result == nullptr) {
return nullptr;
}
std::lock_guard<std::mutex> lock(resultMutex_);
result->exitCode = terminalStatus_;
if (timedOut_) {
result->executionTime = timeoutMs;
} else {
result->exitCode = terminalStatus_;
result->executionTime = (endTimeMs_ > startTime) ? (endTimeMs_ - startTime) : 0;
}
result->outputText = stdoutText_;
result->errorText = stderrText_;
result->signalNumber = signalNumber_;
result->timedOut = timedOut_;
result->executionTime = (endTimeMs_ > startTime) ? (endTimeMs_ - startTime) : 0;
return result;
}
File diff suppressed because it is too large Load Diff
@@ -26,16 +26,18 @@ bool PermissionUtil::VerifyAccessToken(AccessToken::AccessTokenID tokenId,
std::vector<int32_t> perStateList;
auto permRet = AccessToken::AccessTokenKit::VerifyAccessToken(tokenId, requirePermissions,
perStateList);
if (permRet != AccessToken::PermissionState::PERMISSION_GRANTED) {
if (permRet == 0) {
for (size_t index = 0; index < perStateList.size(); index++) {
if (perStateList[index] == AccessToken::TypePermissionState::PERMISSION_DENIED) {
TAG_LOGE(AAFwkTag::CLI_TOOL, "%{public}d not has %{public}s", tokenId,
requirePermissions[index].c_str());
return false;
}
}
return false;
return true;
}
return true;
TAG_LOGE(AAFwkTag::CLI_TOOL, "fail to call VerifyAccessToken");
return false;
}
bool PermissionUtil::VerifyAccessToken(AccessToken::AccessTokenID tokenId, const std::string &requirePermission)
@@ -17,14 +17,26 @@ import("//foundation/ability/ability_runtime/ability_runtime.gni")
group("unittest") {
testonly = true
deps = [
"arg_mapping_test:arg_mapping_test",
"cli_tool_mgr_client_test:cli_tool_mgr_client_test",
"cli_tool_mgr_service_test:cli_tool_mgr_service_test",
"cli_common_util_test:cli_common_util_test",
"cli_event_reply_manager_test:cli_event_reply_manager_test",
"cli_tool_data_manager_test:cli_tool_data_manager_test",
"cli_tool_event_test:cli_tool_event_test",
"cli_tool_mgr_client_test:cli_tool_mgr_client_test",
"cli_tool_mgr_scheduler_recipient_test:cli_tool_mgr_scheduler_recipient_test",
"cli_tool_mgr_service_test:cli_tool_mgr_service_test",
"cli_session_info_test:cli_session_info_test",
"cli_session_subscription_manager_test:cli_session_subscription_manager_test",
"event_dispatcher_test:event_dispatcher_test",
"exec_options_test:exec_options_test",
"exec_result_test:exec_result_test",
"exec_tool_param_test:exec_tool_param_test",
"io_monitor_test:io_monitor_test",
"permission_query_util_test:permission_query_util_test",
"process_manager_test:process_manager_test",
"session_record_test:session_record_test",
"sub_command_info_test:sub_command_info_test",
"tool_summary_test:tool_summary_test",
"tool_info_test:tool_info_test",
"tool_summary_test:tool_summary_test",
"tool_util_test:tool_util_test",
]
}
@@ -0,0 +1,47 @@
# Copyright (c) 2026 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.
import("//build/test.gni")
import("//foundation/ability/ability_runtime/ability_runtime.gni")
module_output_path = "ability_runtime/ability_runtime/clitool"
ohos_unittest("cli_common_util_test") {
module_out_path = module_output_path
include_dirs = [
"mock/include",
"${ability_runtime_services_path}/common/include",
"${cli_tool_framework_path}/services/common/include",
]
sources = [
"cli_common_util_test.cpp",
"mock/src/cli_common_mock.cpp",
"${cli_tool_framework_path}/services/common/src/ccm_util.cpp",
"${cli_tool_framework_path}/services/common/src/permission_util.cpp",
]
external_deps = [
"access_token:libaccesstoken_sdk",
"c_utils:utils",
"googletest:gmock_main",
"googletest:gtest_main",
"hilog:libhilog",
]
}
group("unittest") {
testonly = true
deps = [ ":cli_common_util_test" ]
}
@@ -0,0 +1,106 @@
/*
* Copyright (c) 2026 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 <gtest/gtest.h>
#define private public
#include "ccm_util.h"
#undef private
#include "cli_common_mock.h"
#include "permission_util.h"
using namespace testing::ext;
namespace OHOS {
namespace CliTool {
namespace {
constexpr int32_t CUSTOM_CLI_LIMIT = 16;
constexpr Security::AccessToken::AccessTokenID TEST_TOKEN_ID = 100;
}
class CliCommonUtilTest : public testing::Test {
public:
void SetUp() override
{
CliCommonMock::Reset();
auto &ccmUtil = CcmUtil::GetInstance();
ccmUtil.maxCliQuantity_.isLoaded = false;
ccmUtil.maxCliQuantity_.value = DEFAULT_MAX_CLI_QUANTITY;
}
void TearDown() override
{
CliCommonMock::Reset();
}
};
/**
* @tc.name: CcmUtil_GetCliConcurrencyLimit_0100
* @tc.desc: Test ccm util loads parameter once and then uses cached value
* @tc.type: FUNC
*/
HWTEST_F(CliCommonUtilTest, CcmUtil_GetCliConcurrencyLimit_0100, TestSize.Level1)
{
CliCommonMock::intParameterValue = CUSTOM_CLI_LIMIT;
EXPECT_EQ(CcmUtil::GetInstance().GetCliConcurrencyLimit(), CUSTOM_CLI_LIMIT);
CliCommonMock::intParameterValue = CUSTOM_CLI_LIMIT + 1;
EXPECT_EQ(CcmUtil::GetInstance().GetCliConcurrencyLimit(), CUSTOM_CLI_LIMIT);
}
/**
* @tc.name: PermissionUtil_VerifyAccessToken_0100
* @tc.desc: Test vector permission grant and denial branches
* @tc.type: FUNC
*/
HWTEST_F(CliCommonUtilTest, PermissionUtil_VerifyAccessToken_0100, TestSize.Level1)
{
std::vector<std::string> permissions = {
"ohos.permission.EXEC_CLI_TOOL",
"ohos.permission.QUERY_CLI_TOOL",
};
// Test all permissions granted - permRet should be 0 for success
CliCommonMock::vectorPermissionResult = 0;
CliCommonMock::permissionStateList = {
Security::AccessToken::TypePermissionState::PERMISSION_GRANTED,
Security::AccessToken::TypePermissionState::PERMISSION_GRANTED,
};
EXPECT_TRUE(PermissionUtil::VerifyAccessToken(TEST_TOKEN_ID, permissions));
// Test first permission denied - should return false immediately
CliCommonMock::vectorPermissionResult = 0;
CliCommonMock::permissionStateList = {
Security::AccessToken::TypePermissionState::PERMISSION_DENIED,
Security::AccessToken::TypePermissionState::PERMISSION_GRANTED,
};
EXPECT_FALSE(PermissionUtil::VerifyAccessToken(TEST_TOKEN_ID, permissions));
}
/**
* @tc.name: PermissionUtil_VerifyAccessToken_0200
* @tc.desc: Test single permission grant and denial branches
* @tc.type: FUNC
*/
HWTEST_F(CliCommonUtilTest, PermissionUtil_VerifyAccessToken_0200, TestSize.Level1)
{
CliCommonMock::singlePermissionResult = Security::AccessToken::PermissionState::PERMISSION_GRANTED;
EXPECT_TRUE(PermissionUtil::VerifyAccessToken(TEST_TOKEN_ID, "ohos.permission.EXEC_CLI_TOOL"));
CliCommonMock::singlePermissionResult = Security::AccessToken::PermissionState::PERMISSION_DENIED;
EXPECT_FALSE(PermissionUtil::VerifyAccessToken(TEST_TOKEN_ID, "ohos.permission.EXEC_CLI_TOOL"));
}
} // namespace CliTool
} // namespace OHOS
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2026 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.
*/
#ifndef OHOS_ABILITY_RUNTIME_CLI_COMMON_MOCK_ACCESSTOKEN_KIT_H
#define OHOS_ABILITY_RUNTIME_CLI_COMMON_MOCK_ACCESSTOKEN_KIT_H
#include <string>
#include <vector>
#include "access_token.h"
namespace OHOS {
namespace Security {
namespace AccessToken {
class AccessTokenKit {
public:
static int32_t VerifyAccessToken(AccessTokenID tokenId, const std::vector<std::string> &permissions,
std::vector<int32_t> &permStateList);
static int32_t VerifyAccessToken(AccessTokenID tokenId, const std::string &permissionName, bool crossUser);
};
} // namespace AccessToken
} // namespace Security
} // namespace OHOS
#endif // OHOS_ABILITY_RUNTIME_CLI_COMMON_MOCK_ACCESSTOKEN_KIT_H
@@ -13,21 +13,25 @@
* limitations under the License.
*/
#include "exec_tool_callback_impl.h"
#ifndef OHOS_ABILITY_RUNTIME_CLI_COMMON_MOCK_H
#define OHOS_ABILITY_RUNTIME_CLI_COMMON_MOCK_H
#include "hilog_tag_wrapper.h"
#include <cstdint>
#include <string>
#include <vector>
namespace OHOS {
namespace CliTool {
int32_t ExecToolCallbackImpl::SendResult(const CliSessionInfo &session)
{
TAG_LOGI(AAFwkTag::CLI_TOOL, "ExecToolCallbackImpl send result, sessionId=%{public}s, status=%{public}s",
session.sessionId.c_str(), session.status.c_str());
if (task_) {
TAG_LOGD(AAFwkTag::CLI_TOOL, "ExecToolCallbackImpl invoke callback");
task_(session);
}
return ERR_OK;
}
} // namespace CliTool
} // namespace OHOS
class CliCommonMock {
public:
static int32_t intParameterValue;
static int32_t vectorPermissionResult;
static int32_t singlePermissionResult;
static std::vector<int32_t> permissionStateList;
static void Reset();
};
} // namespace CliTool
} // namespace OHOS
#endif // OHOS_ABILITY_RUNTIME_CLI_COMMON_MOCK_H
@@ -0,0 +1,33 @@
/*
* Copyright (c) 2026 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.
*/
#ifndef OHOS_ABILITY_RUNTIME_CLI_COMMON_MOCK_PARAMETERS_H
#define OHOS_ABILITY_RUNTIME_CLI_COMMON_MOCK_PARAMETERS_H
#include <string>
#include "cli_common_mock.h"
namespace OHOS {
namespace system {
template<typename T>
T GetIntParameter(const std::string &, T)
{
return static_cast<T>(CliTool::CliCommonMock::intParameterValue);
}
} // namespace system
} // namespace OHOS
#endif // OHOS_ABILITY_RUNTIME_CLI_COMMON_MOCK_PARAMETERS_H
@@ -0,0 +1,55 @@
/*
* Copyright (c) 2026 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 "cli_common_mock.h"
#include "accesstoken_kit.h"
namespace OHOS {
namespace CliTool {
int32_t CliCommonMock::intParameterValue = 8;
int32_t CliCommonMock::vectorPermissionResult = Security::AccessToken::PermissionState::PERMISSION_GRANTED;
int32_t CliCommonMock::singlePermissionResult = Security::AccessToken::PermissionState::PERMISSION_GRANTED;
std::vector<int32_t> CliCommonMock::permissionStateList;
void CliCommonMock::Reset()
{
intParameterValue = 8;
vectorPermissionResult = Security::AccessToken::PermissionState::PERMISSION_GRANTED;
singlePermissionResult = Security::AccessToken::PermissionState::PERMISSION_GRANTED;
permissionStateList.clear();
}
} // namespace CliTool
namespace Security {
namespace AccessToken {
int32_t AccessTokenKit::VerifyAccessToken(AccessTokenID, const std::vector<std::string> &permissions,
std::vector<int32_t> &permStateList)
{
if (!CliTool::CliCommonMock::permissionStateList.empty()) {
permStateList = CliTool::CliCommonMock::permissionStateList;
} else {
permStateList.assign(permissions.size(), CliTool::CliCommonMock::vectorPermissionResult);
}
return CliTool::CliCommonMock::vectorPermissionResult;
}
int32_t AccessTokenKit::VerifyAccessToken(AccessTokenID, const std::string &, bool)
{
return CliTool::CliCommonMock::singlePermissionResult;
}
} // namespace AccessToken
} // namespace Security
} // namespace OHOS
@@ -0,0 +1,48 @@
# Copyright (c) 2026 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.
import("//build/test.gni")
import("//foundation/ability/ability_runtime/ability_runtime.gni")
module_output_path = "ability_runtime/ability_runtime/clitool"
ohos_unittest("cli_event_reply_manager_test") {
module_out_path = module_output_path
include_dirs = [
"${ability_runtime_services_path}/common/include",
"${cli_tool_framework_path}/interfaces/cli_tool/include",
"${cli_tool_framework_path}/test/unittest/cli_tool_mgr_client_test/mock/include",
]
sources = [
"cli_event_reply_manager_test.cpp",
"${cli_tool_framework_path}/interfaces/cli_tool/src/cli_event_reply_manager.cpp",
"${cli_tool_framework_path}/interfaces/cli_tool/src/cli_session_info.cpp",
"${cli_tool_framework_path}/interfaces/cli_tool/src/exec_result.cpp",
]
external_deps = [
"ability_base:want",
"c_utils:utils",
"googletest:gmock_main",
"googletest:gtest_main",
"hilog:libhilog",
"ipc:ipc_core",
]
}
group("unittest") {
testonly = true
deps = [ ":cli_event_reply_manager_test" ]
}
@@ -0,0 +1,96 @@
/*
* Copyright (c) 2026 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 <gtest/gtest.h>
#include <optional>
#include <string>
#include "cli_event_reply_manager.h"
using namespace testing::ext;
namespace OHOS {
namespace CliTool {
namespace {
constexpr int32_t ERR_OK = 0;
constexpr int32_t ERROR_CODE = -1;
constexpr int32_t TEST_RESULT_CODE = 1001;
}
class CliEventReplyManagerTest : public testing::Test {
public:
void TearDown() override
{
CliEventReplyManager::GetInstance().ClearAllEvent();
}
};
/**
* @tc.name: CliEventReplyManager_0100
* @tc.desc: Test reply manager active, deferred, missing, null callback and remove branches
* @tc.type: FUNC
*/
HWTEST_F(CliEventReplyManagerTest, CliEventReplyManager_0100, TestSize.Level1)
{
auto &manager = CliEventReplyManager::GetInstance();
int32_t callbackCount = 0;
int32_t callbackCode = 0;
std::string activeEventId = manager.AddEventReplyCallback("active-", [&](const CliEventReplyResult &result) {
callbackCount++;
callbackCode = result.code;
});
manager.ActivateEventReplyCallback(activeEventId);
EXPECT_EQ(manager.HandleEventReply(activeEventId, CliEventReplyResult {TEST_RESULT_CODE, std::nullopt}), ERR_OK);
EXPECT_EQ(callbackCount, 1);
EXPECT_EQ(callbackCode, TEST_RESULT_CODE);
EXPECT_EQ(manager.HandleEventReply(activeEventId, CliEventReplyResult {TEST_RESULT_CODE, std::nullopt}),
ERROR_CODE);
std::optional<CliSessionInfo> deferredSession;
std::string deferredEventId = manager.AddEventReplyCallback("deferred-", [&](const CliEventReplyResult &result) {
callbackCount++;
deferredSession = result.sessionInfo;
});
CliSessionInfo session;
session.sessionId = "session";
session.toolName = "tool";
session.status = "running";
EXPECT_EQ(manager.HandleEventReply(deferredEventId, CliEventReplyResult {ERR_OK, session}), ERR_OK);
EXPECT_EQ(callbackCount, 1);
manager.ActivateEventReplyCallback(deferredEventId);
EXPECT_EQ(callbackCount, 2);
ASSERT_TRUE(deferredSession.has_value());
EXPECT_EQ(deferredSession->sessionId, "session");
std::string nullEventId = manager.AddEventReplyCallback("null-", nullptr);
manager.ActivateEventReplyCallback(nullEventId);
EXPECT_EQ(manager.HandleEventReply(nullEventId, CliEventReplyResult {ERR_OK, std::nullopt}), ERROR_CODE);
std::string removedEventId = manager.AddEventReplyCallback("removed-", [&](const CliEventReplyResult &) {});
manager.RemoveEventReplyCallback(removedEventId);
EXPECT_EQ(manager.HandleEventReply(removedEventId, CliEventReplyResult {ERR_OK, std::nullopt}), ERROR_CODE);
manager.ActivateEventReplyCallback("missing-event");
std::string inactiveEventId = manager.AddEventReplyCallback("inactive-", [&](const CliEventReplyResult &) {
callbackCount++;
});
manager.ActivateEventReplyCallback(inactiveEventId);
EXPECT_EQ(callbackCount, 2);
EXPECT_EQ(manager.HandleEventReply(inactiveEventId, CliEventReplyResult {ERR_OK, std::nullopt}), ERR_OK);
EXPECT_EQ(callbackCount, 3);
}
} // namespace CliTool
} // namespace OHOS
@@ -0,0 +1,45 @@
# Copyright (c) 2026 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.
import("//build/test.gni")
import("//foundation/ability/ability_runtime/ability_runtime.gni")
module_output_path = "ability_runtime/ability_runtime/clitool"
ohos_unittest("cli_session_info_test") {
module_out_path = module_output_path
include_dirs = [
"${ability_runtime_services_path}/common/include",
"${cli_tool_framework_path}/interfaces/cli_tool/include",
]
sources = [
"cli_session_info_test.cpp",
"${cli_tool_framework_path}/interfaces/cli_tool/src/cli_session_info.cpp",
"${cli_tool_framework_path}/interfaces/cli_tool/src/exec_result.cpp",
]
external_deps = [
"c_utils:utils",
"googletest:gmock_main",
"googletest:gtest_main",
"hilog:libhilog",
"ipc:ipc_core",
]
}
group("unittest") {
testonly = true
deps = [ ":cli_session_info_test" ]
}
@@ -0,0 +1,110 @@
/*
* Copyright (c) 2026 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 <gtest/gtest.h>
#include <memory>
#include <parcel.h>
#include "cli_session_info.h"
using namespace testing::ext;
namespace OHOS {
namespace CliTool {
namespace {
constexpr int32_t TEST_EXIT_CODE = 7;
}
class CliSessionInfoTest : public testing::Test {};
/**
* @tc.name: CliSessionInfo_Parcelable_0100
* @tc.desc: Test CliSessionInfo optional result marshalling branches
* @tc.type: FUNC
*/
HWTEST_F(CliSessionInfoTest, CliSessionInfo_Parcelable_0100, TestSize.Level1)
{
CliSessionInfo runningInfo;
runningInfo.sessionId = "session-running";
runningInfo.toolName = "tool";
runningInfo.status = "running";
Parcel runningParcel;
ASSERT_TRUE(runningInfo.Marshalling(runningParcel));
runningParcel.RewindRead(0);
std::unique_ptr<CliSessionInfo> runningResult(CliSessionInfo::Unmarshalling(runningParcel));
ASSERT_NE(runningResult, nullptr);
EXPECT_EQ(runningResult->sessionId, "session-running");
EXPECT_EQ(runningResult->toolName, "tool");
EXPECT_EQ(runningResult->status, "running");
EXPECT_EQ(runningResult->result, nullptr);
CliSessionInfo completedInfo;
completedInfo.sessionId = "session-completed";
completedInfo.toolName = "tool";
completedInfo.status = "completed";
completedInfo.result = std::make_shared<ExecResult>();
completedInfo.result->exitCode = TEST_EXIT_CODE;
completedInfo.result->outputText = "ok";
Parcel completedParcel;
ASSERT_TRUE(completedInfo.Marshalling(completedParcel));
completedParcel.RewindRead(0);
std::unique_ptr<CliSessionInfo> completedResult(CliSessionInfo::Unmarshalling(completedParcel));
ASSERT_NE(completedResult, nullptr);
ASSERT_NE(completedResult->result, nullptr);
EXPECT_EQ(completedResult->status, "completed");
EXPECT_EQ(completedResult->result->exitCode, TEST_EXIT_CODE);
EXPECT_EQ(completedResult->result->outputText, "ok");
}
/**
* @tc.name: CliSessionInfo_Unmarshalling_0200
* @tc.desc: Test CliSessionInfo unmarshalling failure branches
* @tc.type: FUNC
*/
HWTEST_F(CliSessionInfoTest, CliSessionInfo_Unmarshalling_0200, TestSize.Level1)
{
Parcel emptyParcel;
EXPECT_EQ(CliSessionInfo::Unmarshalling(emptyParcel), nullptr);
Parcel missingStatusParcel;
ASSERT_TRUE(missingStatusParcel.WriteString("session"));
ASSERT_TRUE(missingStatusParcel.WriteString("tool"));
missingStatusParcel.RewindRead(0);
EXPECT_EQ(CliSessionInfo::Unmarshalling(missingStatusParcel), nullptr);
Parcel missingToolNameParcel;
ASSERT_TRUE(missingToolNameParcel.WriteString("session"));
missingToolNameParcel.RewindRead(0);
EXPECT_EQ(CliSessionInfo::Unmarshalling(missingToolNameParcel), nullptr);
Parcel missingHasResultParcel;
ASSERT_TRUE(missingHasResultParcel.WriteString("session"));
ASSERT_TRUE(missingHasResultParcel.WriteString("tool"));
ASSERT_TRUE(missingHasResultParcel.WriteString("running"));
missingHasResultParcel.RewindRead(0);
EXPECT_EQ(CliSessionInfo::Unmarshalling(missingHasResultParcel), nullptr);
Parcel missingResultParcel;
ASSERT_TRUE(missingResultParcel.WriteString("session"));
ASSERT_TRUE(missingResultParcel.WriteString("tool"));
ASSERT_TRUE(missingResultParcel.WriteString("completed"));
ASSERT_TRUE(missingResultParcel.WriteBool(true));
missingResultParcel.RewindRead(0);
EXPECT_EQ(CliSessionInfo::Unmarshalling(missingResultParcel), nullptr);
}
} // namespace CliTool
} // namespace OHOS
@@ -0,0 +1,45 @@
# Copyright (c) 2026 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.
import("//build/test.gni")
import("//foundation/ability/ability_runtime/ability_runtime.gni")
module_output_path = "ability_runtime/ability_runtime/clitool"
ohos_unittest("cli_session_subscription_manager_test") {
module_out_path = module_output_path
include_dirs = [
"${ability_runtime_services_path}/common/include",
"${cli_tool_framework_path}/interfaces/cli_tool/include",
]
sources = [
"cli_session_subscription_manager_test.cpp",
"${cli_tool_framework_path}/interfaces/cli_tool/src/cli_session_subscription_manager.cpp",
"${cli_tool_framework_path}/interfaces/cli_tool/src/cli_tool_event.cpp",
]
external_deps = [
"c_utils:utils",
"googletest:gmock_main",
"googletest:gtest_main",
"hilog:libhilog",
"ipc:ipc_core",
]
}
group("unittest") {
testonly = true
deps = [ ":cli_session_subscription_manager_test" ]
}
@@ -0,0 +1,101 @@
/*
* Copyright (c) 2026 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 <gtest/gtest.h>
#include <string>
#include <vector>
#include "cli_session_subscription_manager.h"
using namespace testing::ext;
namespace OHOS {
namespace CliTool {
namespace {
constexpr int32_t ERR_OK = 0;
constexpr int32_t ERROR_CODE = -1;
constexpr int32_t TEST_EXIT_CODE = 7;
}
class CliSessionSubscriptionManagerTest : public testing::Test {
public:
void TearDown() override
{
CliSessionSubscriptionManager::GetInstance().ClearAllSubscriptions();
}
};
/**
* @tc.name: CliSessionSubscriptionManager_0100
* @tc.desc: Test subscription manager active, deferred, exit, invalid and remove branches
* @tc.type: FUNC
*/
HWTEST_F(CliSessionSubscriptionManagerTest, CliSessionSubscriptionManager_0100, TestSize.Level1)
{
auto &manager = CliSessionSubscriptionManager::GetInstance();
CliToolEvent stdoutEvent;
stdoutEvent.type = "stdout";
stdoutEvent.eventData = "hello";
CliToolEvent exitEvent;
exitEvent.type = "exit";
exitEvent.exitCode = TEST_EXIT_CODE;
int32_t callbackCount = 0;
std::vector<std::string> eventTypes;
std::string subscriptionId;
subscriptionId = manager.AddProvisionalSubscription("session", [&](const std::string &sessionId,
const std::string &callbackSubscriptionId, const CliToolEvent &event) {
EXPECT_EQ(sessionId, "session");
EXPECT_EQ(callbackSubscriptionId, subscriptionId);
callbackCount++;
eventTypes.push_back(event.type);
});
ASSERT_FALSE(subscriptionId.empty());
EXPECT_EQ(manager.HandleSessionEvent("session", subscriptionId, stdoutEvent), ERR_OK);
EXPECT_EQ(callbackCount, 0);
manager.ActivateSubscription(subscriptionId);
EXPECT_EQ(callbackCount, 1);
EXPECT_EQ(eventTypes.back(), "stdout");
EXPECT_EQ(manager.HandleSessionEvent("session", subscriptionId, exitEvent), ERR_OK);
EXPECT_EQ(callbackCount, 2);
EXPECT_EQ(eventTypes.back(), "exit");
EXPECT_EQ(manager.HandleSessionEvent("session", subscriptionId, stdoutEvent), ERROR_CODE);
EXPECT_TRUE(manager.AddProvisionalSubscription("", [&](const std::string &, const std::string &,
const CliToolEvent &) {}).empty());
EXPECT_TRUE(manager.AddProvisionalSubscription("session", nullptr).empty());
EXPECT_EQ(manager.HandleSessionEvent("bad-session", "bad-subscription", stdoutEvent), ERROR_CODE);
int32_t pendingExitCount = 0;
std::string pendingExitId = manager.AddProvisionalSubscription("exit-session", [&](const std::string &,
const std::string &, const CliToolEvent &) {
pendingExitCount++;
});
ASSERT_FALSE(pendingExitId.empty());
EXPECT_EQ(manager.HandleSessionEvent("exit-session", pendingExitId, stdoutEvent), ERR_OK);
EXPECT_EQ(manager.HandleSessionEvent("exit-session", pendingExitId, exitEvent), ERR_OK);
manager.ActivateSubscription(pendingExitId);
EXPECT_EQ(pendingExitCount, 2);
EXPECT_EQ(manager.HandleSessionEvent("exit-session", pendingExitId, stdoutEvent), ERROR_CODE);
std::string removedId = manager.AddProvisionalSubscription("remove-session", [&](const std::string &,
const std::string &, const CliToolEvent &) {});
ASSERT_FALSE(removedId.empty());
manager.RemoveSubscription(removedId);
manager.ActivateSubscription(removedId);
EXPECT_EQ(manager.HandleSessionEvent("remove-session", removedId, stdoutEvent), ERROR_CODE);
}
} // namespace CliTool
} // namespace OHOS
@@ -45,12 +45,32 @@ public:
DistributedKv::Status GetEntries(
const DistributedKv::Key &prefix, std::vector<DistributedKv::Entry> &entries) const override
{
if (GetEntries_ != DistributedKv::Status::SUCCESS) {
return GetEntries_;
}
entries.clear();
for (const auto &item : mockData_) {
DistributedKv::Entry entry;
entry.key = DistributedKv::Key(item.first);
entry.value = item.second;
entries.push_back(entry);
}
return GetEntries_;
};
DistributedKv::Status GetEntries(
const DistributedKv::DataQuery &query, std::vector<DistributedKv::Entry> &entries) const override
{
if (GetEntries_ != DistributedKv::Status::SUCCESS) {
return GetEntries_;
}
entries.clear();
for (const auto &item : mockData_) {
DistributedKv::Entry entry;
entry.key = DistributedKv::Key(item.first);
entry.value = item.second;
entries.push_back(entry);
}
return GetEntries_;
};
@@ -231,6 +251,11 @@ public:
mockData_[key] = DistributedKv::Value(value);
}
bool HasMockData(const std::string &key) const
{
return mockData_.find(key) != mockData_.end();
}
DistributedKv::Status GetEntries_ = DistributedKv::Status::SUCCESS;
DistributedKv::Status Delete_ = DistributedKv::Status::SUCCESS;
DistributedKv::Status Put_ = DistributedKv::Status::SUCCESS;
@@ -1,5 +1,5 @@
# Copyright (c) 2026 Huawei Device Co., Ltd.
# Licensed under the Apache License, Version 2.0 (the "License"),
# 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
#
@@ -16,31 +16,25 @@ import("//foundation/ability/ability_runtime/ability_runtime.gni")
module_output_path = "ability_runtime/ability_runtime/clitool"
ohos_unittest("arg_mapping_test") {
ohos_unittest("cli_tool_event_test") {
module_out_path = module_output_path
include_dirs = [ "${cli_tool_framework_path}/interfaces/cli_tool/include" ]
sources = [ "arg_mapping_test.cpp" ]
cflags = []
if (target_cpu == "arm") {
cflags += [ "-BINDER_IPC_32BIT" ]
}
deps = [ "${cli_tool_framework_path}/interfaces/cli_tool:cli_tool_client" ]
sources = [
"cli_tool_event_test.cpp",
"${cli_tool_framework_path}/interfaces/cli_tool/src/cli_tool_event.cpp",
]
external_deps = [
"c_utils:utils",
"googletest:gmock_main",
"googletest:gtest_main",
"hilog:libhilog",
"ipc:ipc_core",
"json:nlohmann_json_static",
]
}
group("unittest") {
testonly = true
deps = [ ":arg_mapping_test" ]
}
deps = [ ":cli_tool_event_test" ]
}
@@ -0,0 +1,79 @@
/*
* Copyright (c) 2026 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 <gtest/gtest.h>
#include <memory>
#include <parcel.h>
#include "cli_tool_event.h"
using namespace testing::ext;
namespace OHOS {
namespace CliTool {
namespace {
constexpr int32_t TEST_EXIT_CODE = 7;
constexpr int64_t TEST_TIMESTAMP = 123456;
}
class CliToolEventTest : public testing::Test {};
/**
* @tc.name: CliToolEvent_Parcelable_0100
* @tc.desc: Test CliToolEvent marshalling and unmarshalling success and failure paths
* @tc.type: FUNC
*/
HWTEST_F(CliToolEventTest, CliToolEvent_Parcelable_0100, TestSize.Level1)
{
CliToolEvent event;
event.type = "stdout";
event.eventData = "payload";
event.exitCode = TEST_EXIT_CODE;
event.timestamp = TEST_TIMESTAMP;
Parcel parcel;
ASSERT_TRUE(event.Marshalling(parcel));
parcel.RewindRead(0);
std::unique_ptr<CliToolEvent> unmarshalled(CliToolEvent::Unmarshalling(parcel));
ASSERT_NE(unmarshalled, nullptr);
EXPECT_EQ(unmarshalled->type, "stdout");
EXPECT_EQ(unmarshalled->eventData, "payload");
EXPECT_EQ(unmarshalled->exitCode, TEST_EXIT_CODE);
EXPECT_EQ(unmarshalled->timestamp, TEST_TIMESTAMP);
Parcel partialParcel;
ASSERT_TRUE(partialParcel.WriteString("exit"));
partialParcel.RewindRead(0);
EXPECT_EQ(CliToolEvent::Unmarshalling(partialParcel), nullptr);
Parcel emptyParcel;
EXPECT_EQ(CliToolEvent::Unmarshalling(emptyParcel), nullptr);
Parcel missingExitCodeParcel;
ASSERT_TRUE(missingExitCodeParcel.WriteString("exit"));
ASSERT_TRUE(missingExitCodeParcel.WriteString("payload"));
missingExitCodeParcel.RewindRead(0);
EXPECT_EQ(CliToolEvent::Unmarshalling(missingExitCodeParcel), nullptr);
Parcel missingTimestampParcel;
ASSERT_TRUE(missingTimestampParcel.WriteString("exit"));
ASSERT_TRUE(missingTimestampParcel.WriteString("payload"));
ASSERT_TRUE(missingTimestampParcel.WriteInt32(TEST_EXIT_CODE));
missingTimestampParcel.RewindRead(0);
EXPECT_EQ(CliToolEvent::Unmarshalling(missingTimestampParcel), nullptr);
}
} // namespace CliTool
} // namespace OHOS
@@ -0,0 +1,73 @@
# Copyright (c) 2026 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.
import("//build/test.gni")
import("//foundation/ability/ability_runtime/ability_runtime.gni")
module_output_path = "ability_runtime/ability_runtime/clitool"
ohos_unittest("cli_tool_mgr_client_test") {
module_out_path = module_output_path
include_dirs = [
"mock/include",
"${cli_tool_framework_path}/interfaces/cli_tool/include",
"${ability_runtime_innerkits_path}/ability_manager/include",
"${ability_runtime_services_path}/common/include",
]
sources = [
"cli_tool_mgr_client_test.cpp",
"mock/src/mock_cli_tool_mgr_scheduler_recipient.cpp",
"mock/src/mock_cli_tool_mgr_service.cpp",
"mock/src/mock_system_ability_client.cpp",
"mock/src/mock_system_ability_manager.cpp",
"${cli_tool_framework_path}/interfaces/cli_tool/src/cli_event_reply_manager.cpp",
"${cli_tool_framework_path}/interfaces/cli_tool/src/cli_mgr_load_callback.cpp",
"${cli_tool_framework_path}/interfaces/cli_tool/src/cli_session_info.cpp",
"${cli_tool_framework_path}/interfaces/cli_tool/src/cli_session_subscription_manager.cpp",
"${cli_tool_framework_path}/interfaces/cli_tool/src/cli_tool_event.cpp",
"${cli_tool_framework_path}/interfaces/cli_tool/src/cli_tool_mgr_client.cpp",
"${cli_tool_framework_path}/interfaces/cli_tool/src/exec_options.cpp",
"${cli_tool_framework_path}/interfaces/cli_tool/src/exec_result.cpp",
"${cli_tool_framework_path}/interfaces/cli_tool/src/exec_tool_param.cpp",
"${cli_tool_framework_path}/interfaces/cli_tool/src/sub_command_info.cpp",
"${cli_tool_framework_path}/interfaces/cli_tool/src/tool_info.cpp",
"${cli_tool_framework_path}/interfaces/cli_tool/src/tool_summary.cpp",
]
cflags = []
if (target_cpu == "arm") {
cflags += [ "-BINDER_IPC_32BIT" ]
}
deps = []
external_deps = [
"ability_base:want",
"c_utils:utils",
"googletest:gmock_main",
"googletest:gtest_main",
"hilog:libhilog",
"hitrace:hitrace_meter",
"ipc:ipc_core",
"json:nlohmann_json_static",
"safwk:system_ability_fwk",
"samgr:samgr_proxy",
]
}
group("unittest") {
testonly = true
deps = [ ":cli_tool_mgr_client_test" ]
}
@@ -0,0 +1,392 @@
/*
* Copyright (c) 2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
*/
#include <gtest/gtest.h>
#include <memory>
#include <string>
#include <vector>
#define private public
#include "cli_event_reply_manager.h"
#include "cli_session_subscription_manager.h"
#include "cli_tool_mgr_client.h"
#undef private
#include "cli_error_code.h"
#include "mock_cli_tool_mgr_client_flag.h"
#include "mock_cli_tool_mgr_service.h"
using namespace testing::ext;
namespace OHOS {
namespace CliTool {
namespace {
ToolInfo BuildToolInfo(const std::string &name)
{
ToolInfo tool;
tool.name = name;
tool.version = "1.0.0";
tool.description = "mock tool";
tool.executablePath = "/system/bin/mock";
tool.inputSchema = "{}";
tool.outputSchema = "{}";
return tool;
}
ToolSummary BuildToolSummary(const std::string &name)
{
ToolSummary summary;
summary.name = name;
summary.version = "1.0.0";
summary.description = "mock summary";
return summary;
}
} // namespace
class MockSessionCallback : public SessionEventCallback {
public:
void OnToolEvent(const std::string &, const std::string &, const CliToolEvent &event) override
{
eventCount++;
lastEventType = event.type;
}
int32_t eventCount = 0;
std::string lastEventType;
};
class CliToolMGRClientTest : public testing::Test {
public:
void SetUp() override
{
CliToolMgrClientFlag::Reset();
CliEventReplyManager::GetInstance().ClearAllEvent();
CliSessionSubscriptionManager::GetInstance().ClearAllSubscriptions();
auto &client = CliToolMGRClient::GetInstance();
client.ClearProxy();
client.loadSaFinished_ = false;
client.serviceDeathHandlers_.clear();
}
void TearDown() override
{
auto &client = CliToolMGRClient::GetInstance();
client.ClearProxy();
CliToolMgrClientFlag::Reset();
}
sptr<MockCliToolMgrService> SetMockService()
{
auto mockService = sptr<MockCliToolMgrService>::MakeSptr();
CliToolMgrClientFlag::cliToolMgr = mockService->AsObject();
CliToolMGRClient::GetInstance().cliToolMgr_ = mockService;
return mockService;
}
};
/**
* @tc.name: GetInstance_0100
* @tc.desc: Test GetInstance returns singleton instance
* @tc.type: FUNC
*/
HWTEST_F(CliToolMGRClientTest, GetInstance_0100, TestSize.Level1)
{
auto &instance1 = CliToolMGRClient::GetInstance();
auto &instance2 = CliToolMGRClient::GetInstance();
EXPECT_EQ(&instance1, &instance2);
}
/**
* @tc.name: GetCliToolMgrProxy_0100
* @tc.desc: Test cached proxy, null system ability and successful load branches
* @tc.type: FUNC
*/
HWTEST_F(CliToolMGRClientTest, GetCliToolMgrProxy_0100, TestSize.Level1)
{
auto &client = CliToolMGRClient::GetInstance();
auto mockService = SetMockService();
EXPECT_EQ(client.GetCliToolMgrProxy()->AsObject(), mockService->AsObject());
client.ClearProxy();
CliToolMgrClientFlag::nullSystemAbility = true;
EXPECT_EQ(client.GetCliToolMgrProxy(), nullptr);
CliToolMgrClientFlag::nullSystemAbility = false;
CliToolMgrClientFlag::cliToolMgr = mockService->AsObject();
EXPECT_EQ(client.GetCliToolMgrProxy()->AsObject(), mockService->AsObject());
}
/**
* @tc.name: LoadCliToolMgrService_0100
* @tc.desc: Test load failure, timeout and success branches
* @tc.type: FUNC
*/
HWTEST_F(CliToolMGRClientTest, LoadCliToolMgrService_0100, TestSize.Level1)
{
auto &client = CliToolMGRClient::GetInstance();
CliToolMgrClientFlag::nullSystemAbility = true;
EXPECT_FALSE(client.LoadCliToolMgrService());
CliToolMgrClientFlag::nullSystemAbility = false;
CliToolMgrClientFlag::retLoadSystemAbility = ERR_INVALID_VALUE;
EXPECT_FALSE(client.LoadCliToolMgrService());
CliToolMgrClientFlag::retLoadSystemAbility = ERR_OK;
CliToolMgrClientFlag::shouldCallback = true;
CliToolMgrClientFlag::cliToolMgr = sptr<MockCliToolMgrService>::MakeSptr()->AsObject();
EXPECT_TRUE(client.LoadCliToolMgrService());
}
/**
* @tc.name: QueryInterfaces_0100
* @tc.desc: Test query/register interfaces return proxy results and populate outputs
* @tc.type: FUNC
*/
HWTEST_F(CliToolMGRClientTest, QueryInterfaces_0100, TestSize.Level1)
{
SetMockService();
CliToolMgrClientFlag::summaries = {BuildToolSummary("ohos-summary")};
CliToolMgrClientFlag::toolInfos = {BuildToolInfo("ohos-tool")};
std::vector<ToolSummary> summaries;
EXPECT_EQ(CliToolMGRClient::GetInstance().GetAllToolSummaries(summaries), ERR_OK);
ASSERT_EQ(summaries.size(), 1u);
EXPECT_EQ(summaries[0].name, "ohos-summary");
ToolInfo tool;
EXPECT_EQ(CliToolMGRClient::GetInstance().GetToolInfoByName("ohos-tool", tool), ERR_OK);
EXPECT_EQ(tool.name, "ohos-tool");
std::vector<ToolInfo> tools;
EXPECT_EQ(CliToolMGRClient::GetInstance().GetAllToolInfos(tools), ERR_OK);
ASSERT_EQ(tools.size(), 1u);
EXPECT_EQ(tools[0].name, "ohos-tool");
EXPECT_EQ(CliToolMGRClient::GetInstance().RegisterTool(tool), ERR_OK);
}
/**
* @tc.name: QueryInterfaces_0200
* @tc.desc: Test proxy error branches for query/register interfaces
* @tc.type: FUNC
*/
HWTEST_F(CliToolMGRClientTest, QueryInterfaces_0200, TestSize.Level1)
{
SetMockService();
CliToolMgrClientFlag::retGetAllToolSummaries = ERR_INVALID_VALUE;
CliToolMgrClientFlag::retGetToolInfoByName = ERR_INVALID_VALUE;
CliToolMgrClientFlag::retGetAllToolInfos = ERR_INVALID_VALUE;
CliToolMgrClientFlag::retRegisterTool = ERR_INVALID_VALUE;
std::vector<ToolSummary> summaries;
ToolInfo tool;
std::vector<ToolInfo> tools;
EXPECT_EQ(CliToolMGRClient::GetInstance().GetAllToolSummaries(summaries), ERR_INVALID_VALUE);
EXPECT_EQ(CliToolMGRClient::GetInstance().GetToolInfoByName("ohos-tool", tool), ERR_INVALID_VALUE);
EXPECT_EQ(CliToolMGRClient::GetInstance().GetAllToolInfos(tools), ERR_INVALID_VALUE);
EXPECT_EQ(CliToolMGRClient::GetInstance().RegisterTool(tool), ERR_INVALID_VALUE);
}
/**
* @tc.name: NullProxyInterfaces_0100
* @tc.desc: Test public interfaces return service-connect failure when proxy cannot be loaded
* @tc.type: FUNC
*/
HWTEST_F(CliToolMGRClientTest, NullProxyInterfaces_0100, TestSize.Level1)
{
CliToolMgrClientFlag::nullSystemAbility = true;
std::vector<ToolSummary> summaries;
ToolInfo tool;
std::vector<ToolInfo> tools;
std::vector<Command> commands;
std::vector<CommandPermission> permissions;
CliSessionInfo session;
std::string subscriptionId;
EXPECT_EQ(CliToolMGRClient::GetInstance().GetAllToolSummaries(summaries), GET_CLI_TOOL_MGR_SERVICE_FAILED);
EXPECT_EQ(CliToolMGRClient::GetInstance().GetToolInfoByName("tool", tool), GET_CLI_TOOL_MGR_SERVICE_FAILED);
EXPECT_EQ(CliToolMGRClient::GetInstance().GetAllToolInfos(tools), GET_CLI_TOOL_MGR_SERVICE_FAILED);
EXPECT_EQ(CliToolMGRClient::GetInstance().RegisterTool(tool), GET_CLI_TOOL_MGR_SERVICE_FAILED);
EXPECT_EQ(CliToolMGRClient::GetInstance().ExecTool(ExecToolParam {}, nullptr), GET_CLI_TOOL_MGR_SERVICE_FAILED);
EXPECT_EQ(CliToolMGRClient::GetInstance().SubscribeSession("session", std::make_shared<MockSessionCallback>(),
subscriptionId), GET_CLI_TOOL_MGR_SERVICE_FAILED);
EXPECT_EQ(CliToolMGRClient::GetInstance().UnsubscribeSession("session", "sub"), GET_CLI_TOOL_MGR_SERVICE_FAILED);
EXPECT_EQ(CliToolMGRClient::GetInstance().ClearSession("session"), GET_CLI_TOOL_MGR_SERVICE_FAILED);
EXPECT_EQ(CliToolMGRClient::GetInstance().QuerySession("session", session), GET_CLI_TOOL_MGR_SERVICE_FAILED);
EXPECT_EQ(CliToolMGRClient::GetInstance().SendMessage("session", "input", nullptr),
GET_CLI_TOOL_MGR_SERVICE_FAILED);
EXPECT_EQ(CliToolMGRClient::GetInstance().BatchQueryPermissionBySubCommand(commands, permissions),
GET_CLI_TOOL_MGR_SERVICE_FAILED);
}
/**
* @tc.name: ExecTool_0100
* @tc.desc: Test scheduler failure, execute failure cleanup and success callback activation
* @tc.type: FUNC
*/
HWTEST_F(CliToolMGRClientTest, ExecTool_0100, TestSize.Level1)
{
SetMockService();
ExecToolParam param;
param.toolName = "ohos-tool";
int32_t callbackCode = -1;
CliToolMgrClientFlag::retRegisterScheduler = ERR_INVALID_VALUE;
EXPECT_EQ(CliToolMGRClient::GetInstance().ExecTool(param,
[&callbackCode](int32_t code, const CliSessionInfo &) { callbackCode = code; }), ERR_INVALID_VALUE);
CliToolMGRClient::GetInstance().schedulerRegistered_ = false;
CliToolMgrClientFlag::retRegisterScheduler = ERR_OK;
CliToolMgrClientFlag::retExecTool = ERR_INVALID_VALUE;
EXPECT_EQ(CliToolMGRClient::GetInstance().ExecTool(param,
[&callbackCode](int32_t code, const CliSessionInfo &) { callbackCode = code; }), ERR_INVALID_VALUE);
EXPECT_EQ(CliEventReplyManager::GetInstance().HandleEventReply(
CliToolMgrClientFlag::lastEventId, CliEventReplyResult {}), -1);
CliToolMgrClientFlag::retExecTool = ERR_OK;
EXPECT_EQ(CliToolMGRClient::GetInstance().ExecTool(param,
[&callbackCode](int32_t code, const CliSessionInfo &) { callbackCode = code; }), ERR_OK);
CliSessionInfo session;
session.sessionId = "session";
CliEventReplyResult result;
result.code = ERR_OK;
result.sessionInfo = session;
EXPECT_EQ(CliEventReplyManager::GetInstance().HandleEventReply(CliToolMgrClientFlag::lastEventId, result), ERR_OK);
EXPECT_EQ(callbackCode, ERR_OK);
}
/**
* @tc.name: SessionInterfaces_0100
* @tc.desc: Test subscribe/unsubscribe/query/clear interfaces
* @tc.type: FUNC
*/
HWTEST_F(CliToolMGRClientTest, SessionInterfaces_0100, TestSize.Level1)
{
SetMockService();
std::string subscriptionId;
auto callback = std::make_shared<MockSessionCallback>();
EXPECT_EQ(CliToolMGRClient::GetInstance().SubscribeSession("session", callback, subscriptionId), ERR_OK);
EXPECT_FALSE(subscriptionId.empty());
CliToolEvent event;
event.type = "stdout";
EXPECT_EQ(CliSessionSubscriptionManager::GetInstance().HandleSessionEvent("session", subscriptionId, event),
ERR_OK);
EXPECT_EQ(callback->eventCount, 1);
EXPECT_EQ(callback->lastEventType, "stdout");
EXPECT_EQ(CliToolMGRClient::GetInstance().UnsubscribeSession("session", subscriptionId), ERR_OK);
EXPECT_EQ(CliToolMGRClient::GetInstance().ClearSession("session"), ERR_OK);
CliToolMgrClientFlag::querySession.sessionId = "session";
CliSessionInfo session;
EXPECT_EQ(CliToolMGRClient::GetInstance().QuerySession("session", session), ERR_OK);
EXPECT_EQ(session.sessionId, "session");
}
/**
* @tc.name: SessionInterfaces_0200
* @tc.desc: Test subscribe failure removes provisional subscription and direct session proxy errors
* @tc.type: FUNC
*/
HWTEST_F(CliToolMGRClientTest, SessionInterfaces_0200, TestSize.Level1)
{
SetMockService();
CliToolMgrClientFlag::retSubscribeSession = ERR_INVALID_VALUE;
std::string subscriptionId;
EXPECT_EQ(CliToolMGRClient::GetInstance().SubscribeSession(
"session", std::make_shared<MockSessionCallback>(), subscriptionId), ERR_INVALID_VALUE);
EXPECT_TRUE(subscriptionId.empty());
CliToolMgrClientFlag::retUnsubscribeSession = ERR_INVALID_VALUE;
CliToolMgrClientFlag::retClearSession = ERR_INVALID_VALUE;
CliToolMgrClientFlag::retQuerySession = ERR_INVALID_VALUE;
CliSessionInfo session;
EXPECT_EQ(CliToolMGRClient::GetInstance().UnsubscribeSession("session", "sub"), ERR_INVALID_VALUE);
EXPECT_EQ(CliToolMGRClient::GetInstance().ClearSession("session"), ERR_INVALID_VALUE);
EXPECT_EQ(CliToolMGRClient::GetInstance().QuerySession("session", session), ERR_INVALID_VALUE);
}
/**
* @tc.name: SendMessage_0100
* @tc.desc: Test send message failure cleanup and success callback activation
* @tc.type: FUNC
*/
HWTEST_F(CliToolMGRClientTest, SendMessage_0100, TestSize.Level1)
{
SetMockService();
int32_t callbackCode = -1;
CliToolMgrClientFlag::retSendMessage = ERR_INVALID_VALUE;
EXPECT_EQ(CliToolMGRClient::GetInstance().SendMessage(
"session", "input", [&callbackCode](int32_t code) { callbackCode = code; }), ERR_INVALID_VALUE);
EXPECT_EQ(CliEventReplyManager::GetInstance().HandleEventReply(
CliToolMgrClientFlag::lastEventId, CliEventReplyResult {}), -1);
CliToolMgrClientFlag::retSendMessage = ERR_OK;
EXPECT_EQ(CliToolMGRClient::GetInstance().SendMessage(
"session", "input", [&callbackCode](int32_t code) { callbackCode = code; }), ERR_OK);
CliEventReplyResult result;
result.code = ERR_OK;
EXPECT_EQ(CliEventReplyManager::GetInstance().HandleEventReply(CliToolMgrClientFlag::lastEventId, result), ERR_OK);
EXPECT_EQ(callbackCode, ERR_OK);
}
/**
* @tc.name: BatchQueryPermission_0100
* @tc.desc: Test batch query permission success and failure forwarding
* @tc.type: FUNC
*/
HWTEST_F(CliToolMGRClientTest, BatchQueryPermission_0100, TestSize.Level1)
{
SetMockService();
CommandPermission permission;
permission.cmd.toolName = "ohos-tool";
permission.permissions = {"ohos.permission.TEST"};
CliToolMgrClientFlag::commandPermissions = {permission};
std::vector<Command> commands = {Command {"ohos-tool", ""}};
std::vector<CommandPermission> permissions;
EXPECT_EQ(CliToolMGRClient::GetInstance().BatchQueryPermissionBySubCommand(commands, permissions), ERR_OK);
ASSERT_EQ(permissions.size(), 1u);
EXPECT_EQ(permissions[0].permissions[0], "ohos.permission.TEST");
CliToolMgrClientFlag::retBatchQueryPermission = ERR_INVALID_VALUE;
EXPECT_EQ(CliToolMGRClient::GetInstance().BatchQueryPermissionBySubCommand(commands, permissions),
ERR_INVALID_VALUE);
}
/**
* @tc.name: ProxyLifecycle_0100
* @tc.desc: Test callbacks, clear proxy and death recipient branches
* @tc.type: FUNC
*/
HWTEST_F(CliToolMGRClientTest, ProxyLifecycle_0100, TestSize.Level1)
{
auto &client = CliToolMGRClient::GetInstance();
auto mockService = SetMockService();
client.schedulerRegistered_ = true;
bool deathHandlerCalled = false;
client.serviceDeathHandlers_.push_back([&deathHandlerCalled]() { deathHandlerCalled = true; });
client.ClearProxy();
EXPECT_EQ(client.cliToolMgr_, nullptr);
EXPECT_FALSE(client.schedulerRegistered_);
EXPECT_TRUE(deathHandlerCalled);
client.OnLoadSystemAbilitySuccess(mockService->AsObject());
EXPECT_NE(client.cliToolMgr_, nullptr);
EXPECT_TRUE(client.loadSaFinished_);
client.OnLoadSystemAbilityFail();
EXPECT_EQ(client.cliToolMgr_, nullptr);
EXPECT_TRUE(client.loadSaFinished_);
bool recipientCalled = false;
CliToolMGRClient::CliMgrDeathRecipient recipient(
[&recipientCalled](const wptr<IRemoteObject> &) { recipientCalled = true; });
recipient.OnRemoteDied(nullptr);
EXPECT_TRUE(recipientCalled);
}
} // namespace CliTool
} // namespace OHOS
@@ -0,0 +1,25 @@
/*
* Copyright (c) 2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
*/
#ifndef OHOS_ABILITY_RUNTIME_MOCK_CLI_TOOL_MGR_SCHEDULER_RECIPIENT_H
#define OHOS_ABILITY_RUNTIME_MOCK_CLI_TOOL_MGR_SCHEDULER_RECIPIENT_H
#include "icli_tool_manager_scheduler.h"
#include "iremote_stub.h"
namespace OHOS {
namespace CliTool {
class CliToolManagerSchedulerRecipient : public IRemoteStub<ICliToolManagerScheduler> {
public:
int32_t SchedulerSessionEvent(
const std::string &sessionId, const std::string &subscriptionId, const CliToolEvent &event) override;
int32_t SchedulerInputReplyEvent(const std::string &eventId, int32_t resultCode) override;
int32_t SchedulerExecToolReplyEvent(
const std::string &eventId, int32_t resultCode, const CliSessionInfo &session) override;
};
} // namespace CliTool
} // namespace OHOS
#endif // OHOS_ABILITY_RUNTIME_MOCK_CLI_TOOL_MGR_SCHEDULER_RECIPIENT_H
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
*/
#ifndef OHOS_ABILITY_RUNTIME_MOCK_ICLI_TOOL_DATA_H
#define OHOS_ABILITY_RUNTIME_MOCK_ICLI_TOOL_DATA_H
#include <string>
#include <vector>
namespace OHOS {
namespace CliTool {
struct Command {
std::string toolName;
std::string subCommand;
};
struct CommandPermission {
Command cmd;
std::vector<std::string> permissions;
int32_t queryRet = 0;
};
} // namespace CliTool
} // namespace OHOS
#endif // OHOS_ABILITY_RUNTIME_MOCK_ICLI_TOOL_DATA_H
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
*/
#ifndef OHOS_ABILITY_RUNTIME_MOCK_ICLI_TOOL_MANAGER_H
#define OHOS_ABILITY_RUNTIME_MOCK_ICLI_TOOL_MANAGER_H
#include "cli_session_info.h"
#include "exec_tool_param.h"
#include "icli_tool_data.h"
#include "icli_tool_manager_scheduler.h"
#include "iremote_broker.h"
#include "tool_info.h"
#include "tool_summary.h"
namespace OHOS {
namespace CliTool {
class ICliToolManager : public IRemoteBroker {
public:
DECLARE_INTERFACE_DESCRIPTOR(u"OHOS.CliTool.ICliToolManager")
virtual int32_t GetAllToolSummaries(std::vector<ToolSummary> &summaries) = 0;
virtual int32_t GetToolInfoByName(const std::string &name, ToolInfo &tool) = 0;
virtual int32_t GetAllToolInfos(ToolsRawData &tools) = 0;
virtual int32_t RegisterTool(const ToolInfo &tool) = 0;
virtual int32_t ExecTool(const ExecToolParam &param, const std::string &eventId) = 0;
virtual int32_t SubscribeSession(const std::string &sessionId, const std::string &subscriptionId) = 0;
virtual int32_t UnsubscribeSession(const std::string &sessionId, const std::string &subscriptionId) = 0;
virtual int32_t ClearSession(const std::string &sessionId) = 0;
virtual int32_t QuerySession(const std::string &sessionId, CliSessionInfo &session) = 0;
virtual int32_t SendMessage(const std::string &sessionId, const std::string &inputText,
const std::string &eventId) = 0;
virtual int32_t RegisterScheduler(const sptr<ICliToolManagerScheduler> &scheduler) = 0;
virtual int32_t UnregisterScheduler() = 0;
virtual int32_t BatchQueryPermissionBySubCommand(
const std::vector<Command> &cmds, std::vector<CommandPermission> &cmdPermissions) = 0;
};
} // namespace CliTool
} // namespace OHOS
#endif // OHOS_ABILITY_RUNTIME_MOCK_ICLI_TOOL_MANAGER_H
@@ -0,0 +1,28 @@
/*
* Copyright (c) 2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
*/
#ifndef OHOS_ABILITY_RUNTIME_MOCK_ICLI_TOOL_MANAGER_SCHEDULER_H
#define OHOS_ABILITY_RUNTIME_MOCK_ICLI_TOOL_MANAGER_SCHEDULER_H
#include "cli_session_info.h"
#include "cli_tool_event.h"
#include "iremote_broker.h"
namespace OHOS {
namespace CliTool {
class ICliToolManagerScheduler : public IRemoteBroker {
public:
DECLARE_INTERFACE_DESCRIPTOR(u"OHOS.CliTool.ICliToolManagerScheduler")
virtual int32_t SchedulerSessionEvent(
const std::string &sessionId, const std::string &subscriptionId, const CliToolEvent &event) = 0;
virtual int32_t SchedulerInputReplyEvent(const std::string &eventId, int32_t resultCode) = 0;
virtual int32_t SchedulerExecToolReplyEvent(
const std::string &eventId, int32_t resultCode, const CliSessionInfo &session) = 0;
};
} // namespace CliTool
} // namespace OHOS
#endif // OHOS_ABILITY_RUNTIME_MOCK_ICLI_TOOL_MANAGER_SCHEDULER_H
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
*/
#ifndef OHOS_ABILITY_RUNTIME_MOCK_IF_SYSTEM_ABILITY_MANAGER_H
#define OHOS_ABILITY_RUNTIME_MOCK_IF_SYSTEM_ABILITY_MANAGER_H
#include "iremote_broker.h"
#include "system_ability_load_callback_stub.h"
namespace OHOS {
class ISystemAbilityManager : public IRemoteBroker {
public:
DECLARE_INTERFACE_DESCRIPTOR(u"OHOS.ISystemAbilityManager")
virtual int32_t LoadSystemAbility(int32_t systemAbilityId, const sptr<ISystemAbilityLoadCallback> &callback) = 0;
};
} // namespace OHOS
#endif // OHOS_ABILITY_RUNTIME_MOCK_IF_SYSTEM_ABILITY_MANAGER_H
@@ -0,0 +1,23 @@
/*
* Copyright (c) 2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
*/
#ifndef OHOS_ABILITY_RUNTIME_MOCK_ISERVICE_REGISTRY_H
#define OHOS_ABILITY_RUNTIME_MOCK_ISERVICE_REGISTRY_H
#include "if_system_ability_manager.h"
namespace OHOS {
class SystemAbilityManagerClient {
public:
static SystemAbilityManagerClient &GetInstance();
sptr<ISystemAbilityManager> GetSystemAbilityManager();
private:
SystemAbilityManagerClient() = default;
~SystemAbilityManagerClient() = default;
};
} // namespace OHOS
#endif // OHOS_ABILITY_RUNTIME_MOCK_ISERVICE_REGISTRY_H
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
*/
#ifndef OHOS_ABILITY_RUNTIME_MOCK_CLI_TOOL_MGR_CLIENT_FLAG_H
#define OHOS_ABILITY_RUNTIME_MOCK_CLI_TOOL_MGR_CLIENT_FLAG_H
#include <string>
#include <vector>
#include "cli_session_info.h"
#include "icli_tool_data.h"
#include "iremote_object.h"
#include "tool_info.h"
#include "tool_summary.h"
namespace OHOS {
namespace CliTool {
class CliToolMgrClientFlag {
public:
static int32_t retGetAllToolSummaries;
static int32_t retGetToolInfoByName;
static int32_t retGetAllToolInfos;
static int32_t retRegisterTool;
static int32_t retExecTool;
static int32_t retSubscribeSession;
static int32_t retUnsubscribeSession;
static int32_t retClearSession;
static int32_t retQuerySession;
static int32_t retSendMessage;
static int32_t retRegisterScheduler;
static int32_t retBatchQueryPermission;
static int32_t retLoadSystemAbility;
static bool nullSystemAbility;
static bool shouldCallback;
static sptr<IRemoteObject> cliToolMgr;
static std::string lastEventId;
static std::string lastSubscriptionId;
static std::vector<ToolInfo> toolInfos;
static std::vector<ToolSummary> summaries;
static CliSessionInfo querySession;
static std::vector<CommandPermission> commandPermissions;
static void Reset();
};
} // namespace CliTool
} // namespace OHOS
#endif // OHOS_ABILITY_RUNTIME_MOCK_CLI_TOOL_MGR_CLIENT_FLAG_H
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
*/
#ifndef OHOS_ABILITY_RUNTIME_MOCK_CLI_TOOL_MGR_SERVICE_H
#define OHOS_ABILITY_RUNTIME_MOCK_CLI_TOOL_MGR_SERVICE_H
#include "icli_tool_manager.h"
#include "iremote_stub.h"
namespace OHOS {
namespace CliTool {
class MockCliToolMgrService : public IRemoteStub<ICliToolManager> {
public:
int32_t GetAllToolSummaries(std::vector<ToolSummary> &summaries) override;
int32_t GetToolInfoByName(const std::string &name, ToolInfo &tool) override;
int32_t GetAllToolInfos(ToolsRawData &tools) override;
int32_t RegisterTool(const ToolInfo &tool) override;
int32_t ExecTool(const ExecToolParam &param, const std::string &eventId) override;
int32_t SubscribeSession(const std::string &sessionId, const std::string &subscriptionId) override;
int32_t UnsubscribeSession(const std::string &sessionId, const std::string &subscriptionId) override;
int32_t ClearSession(const std::string &sessionId) override;
int32_t QuerySession(const std::string &sessionId, CliSessionInfo &session) override;
int32_t SendMessage(const std::string &sessionId, const std::string &inputText,
const std::string &eventId) override;
int32_t RegisterScheduler(const sptr<ICliToolManagerScheduler> &scheduler) override;
int32_t UnregisterScheduler() override;
int32_t BatchQueryPermissionBySubCommand(
const std::vector<Command> &cmds, std::vector<CommandPermission> &cmdPermissions) override;
};
} // namespace CliTool
} // namespace OHOS
#endif // OHOS_ABILITY_RUNTIME_MOCK_CLI_TOOL_MGR_SERVICE_H
@@ -0,0 +1,21 @@
/*
* Copyright (c) 2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
*/
#ifndef OHOS_ABILITY_RUNTIME_MOCK_CLI_SYSTEM_ABILITY_MANAGER_H
#define OHOS_ABILITY_RUNTIME_MOCK_CLI_SYSTEM_ABILITY_MANAGER_H
#include "if_system_ability_manager.h"
#include "iremote_stub.h"
namespace OHOS {
namespace CliTool {
class MockSystemAbilityManager : public IRemoteStub<ISystemAbilityManager> {
public:
int32_t LoadSystemAbility(int32_t systemAbilityId, const sptr<ISystemAbilityLoadCallback> &callback) override;
};
} // namespace CliTool
} // namespace OHOS
#endif // OHOS_ABILITY_RUNTIME_MOCK_CLI_SYSTEM_ABILITY_MANAGER_H
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
*/
#include "cli_tool_mgr_scheduler_recipient.h"
#include "cli_event_reply_manager.h"
#include "cli_session_subscription_manager.h"
namespace OHOS {
namespace CliTool {
int32_t CliToolManagerSchedulerRecipient::SchedulerSessionEvent(
const std::string &sessionId, const std::string &subscriptionId, const CliToolEvent &event)
{
return CliSessionSubscriptionManager::GetInstance().HandleSessionEvent(sessionId, subscriptionId, event);
}
int32_t CliToolManagerSchedulerRecipient::SchedulerInputReplyEvent(const std::string &eventId, int32_t resultCode)
{
CliEventReplyResult result;
result.code = resultCode;
return CliEventReplyManager::GetInstance().HandleEventReply(eventId, result);
}
int32_t CliToolManagerSchedulerRecipient::SchedulerExecToolReplyEvent(
const std::string &eventId, int32_t resultCode, const CliSessionInfo &session)
{
CliEventReplyResult result;
result.code = resultCode;
result.sessionInfo = session;
return CliEventReplyManager::GetInstance().HandleEventReply(eventId, result);
}
} // namespace CliTool
} // namespace OHOS
@@ -0,0 +1,140 @@
/*
* Copyright (c) 2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
*/
#include "mock_cli_tool_mgr_service.h"
#include "cli_error_code.h"
#include "mock_cli_tool_mgr_client_flag.h"
namespace OHOS {
namespace CliTool {
int32_t CliToolMgrClientFlag::retGetAllToolSummaries = ERR_OK;
int32_t CliToolMgrClientFlag::retGetToolInfoByName = ERR_OK;
int32_t CliToolMgrClientFlag::retGetAllToolInfos = ERR_OK;
int32_t CliToolMgrClientFlag::retRegisterTool = ERR_OK;
int32_t CliToolMgrClientFlag::retExecTool = ERR_OK;
int32_t CliToolMgrClientFlag::retSubscribeSession = ERR_OK;
int32_t CliToolMgrClientFlag::retUnsubscribeSession = ERR_OK;
int32_t CliToolMgrClientFlag::retClearSession = ERR_OK;
int32_t CliToolMgrClientFlag::retQuerySession = ERR_OK;
int32_t CliToolMgrClientFlag::retSendMessage = ERR_OK;
int32_t CliToolMgrClientFlag::retRegisterScheduler = ERR_OK;
int32_t CliToolMgrClientFlag::retBatchQueryPermission = ERR_OK;
int32_t CliToolMgrClientFlag::retLoadSystemAbility = ERR_OK;
bool CliToolMgrClientFlag::nullSystemAbility = false;
bool CliToolMgrClientFlag::shouldCallback = true;
sptr<IRemoteObject> CliToolMgrClientFlag::cliToolMgr = nullptr;
std::string CliToolMgrClientFlag::lastEventId;
std::string CliToolMgrClientFlag::lastSubscriptionId;
std::vector<ToolInfo> CliToolMgrClientFlag::toolInfos;
std::vector<ToolSummary> CliToolMgrClientFlag::summaries;
CliSessionInfo CliToolMgrClientFlag::querySession;
std::vector<CommandPermission> CliToolMgrClientFlag::commandPermissions;
void CliToolMgrClientFlag::Reset()
{
retGetAllToolSummaries = ERR_OK;
retGetToolInfoByName = ERR_OK;
retGetAllToolInfos = ERR_OK;
retRegisterTool = ERR_OK;
retExecTool = ERR_OK;
retSubscribeSession = ERR_OK;
retUnsubscribeSession = ERR_OK;
retClearSession = ERR_OK;
retQuerySession = ERR_OK;
retSendMessage = ERR_OK;
retRegisterScheduler = ERR_OK;
retBatchQueryPermission = ERR_OK;
retLoadSystemAbility = ERR_OK;
nullSystemAbility = false;
shouldCallback = true;
cliToolMgr = nullptr;
lastEventId.clear();
lastSubscriptionId.clear();
toolInfos.clear();
summaries.clear();
querySession = {};
commandPermissions.clear();
}
int32_t MockCliToolMgrService::GetAllToolSummaries(std::vector<ToolSummary> &summaries)
{
summaries = CliToolMgrClientFlag::summaries;
return CliToolMgrClientFlag::retGetAllToolSummaries;
}
int32_t MockCliToolMgrService::GetToolInfoByName(const std::string &, ToolInfo &tool)
{
if (!CliToolMgrClientFlag::toolInfos.empty()) {
tool = CliToolMgrClientFlag::toolInfos.front();
}
return CliToolMgrClientFlag::retGetToolInfoByName;
}
int32_t MockCliToolMgrService::GetAllToolInfos(ToolsRawData &tools)
{
if (CliToolMgrClientFlag::retGetAllToolInfos == ERR_OK) {
ToolsRawData::FromToolInfoVec(CliToolMgrClientFlag::toolInfos, tools);
}
return CliToolMgrClientFlag::retGetAllToolInfos;
}
int32_t MockCliToolMgrService::RegisterTool(const ToolInfo &)
{
return CliToolMgrClientFlag::retRegisterTool;
}
int32_t MockCliToolMgrService::ExecTool(const ExecToolParam &, const std::string &eventId)
{
CliToolMgrClientFlag::lastEventId = eventId;
return CliToolMgrClientFlag::retExecTool;
}
int32_t MockCliToolMgrService::SubscribeSession(const std::string &, const std::string &subscriptionId)
{
CliToolMgrClientFlag::lastSubscriptionId = subscriptionId;
return CliToolMgrClientFlag::retSubscribeSession;
}
int32_t MockCliToolMgrService::UnsubscribeSession(const std::string &, const std::string &)
{
return CliToolMgrClientFlag::retUnsubscribeSession;
}
int32_t MockCliToolMgrService::ClearSession(const std::string &)
{
return CliToolMgrClientFlag::retClearSession;
}
int32_t MockCliToolMgrService::QuerySession(const std::string &, CliSessionInfo &session)
{
session = CliToolMgrClientFlag::querySession;
return CliToolMgrClientFlag::retQuerySession;
}
int32_t MockCliToolMgrService::SendMessage(const std::string &, const std::string &, const std::string &eventId)
{
CliToolMgrClientFlag::lastEventId = eventId;
return CliToolMgrClientFlag::retSendMessage;
}
int32_t MockCliToolMgrService::RegisterScheduler(const sptr<ICliToolManagerScheduler> &)
{
return CliToolMgrClientFlag::retRegisterScheduler;
}
int32_t MockCliToolMgrService::UnregisterScheduler()
{
return ERR_OK;
}
int32_t MockCliToolMgrService::BatchQueryPermissionBySubCommand(
const std::vector<Command> &, std::vector<CommandPermission> &cmdPermissions)
{
cmdPermissions = CliToolMgrClientFlag::commandPermissions;
return CliToolMgrClientFlag::retBatchQueryPermission;
}
} // namespace CliTool
} // namespace OHOS
@@ -0,0 +1,24 @@
/*
* Copyright (c) 2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
*/
#include "iservice_registry.h"
#include "mock_cli_tool_mgr_client_flag.h"
#include "mock_system_ability_manager.h"
namespace OHOS {
SystemAbilityManagerClient &SystemAbilityManagerClient::GetInstance()
{
static SystemAbilityManagerClient instance;
return instance;
}
sptr<ISystemAbilityManager> SystemAbilityManagerClient::GetSystemAbilityManager()
{
if (CliTool::CliToolMgrClientFlag::nullSystemAbility) {
return nullptr;
}
return sptr<CliTool::MockSystemAbilityManager>::MakeSptr();
}
} // namespace OHOS
@@ -0,0 +1,25 @@
/*
* Copyright (c) 2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
*/
#include "mock_system_ability_manager.h"
#include "cli_error_code.h"
#include "mock_cli_tool_mgr_client_flag.h"
namespace OHOS {
namespace CliTool {
int32_t MockSystemAbilityManager::LoadSystemAbility(
int32_t systemAbilityId, const sptr<ISystemAbilityLoadCallback> &callback)
{
if (CliToolMgrClientFlag::retLoadSystemAbility != ERR_OK) {
return CliToolMgrClientFlag::retLoadSystemAbility;
}
if (CliToolMgrClientFlag::shouldCallback) {
callback->OnLoadSystemAbilitySuccess(systemAbilityId, CliToolMgrClientFlag::cliToolMgr);
}
return ERR_OK;
}
} // namespace CliTool
} // namespace OHOS
@@ -0,0 +1,52 @@
# Copyright (c) 2026 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.
import("//build/test.gni")
import("//foundation/ability/ability_runtime/ability_runtime.gni")
module_output_path = "ability_runtime/ability_runtime/clitool"
ohos_unittest("cli_tool_mgr_scheduler_recipient_test") {
module_out_path = module_output_path
include_dirs = [
"${ability_runtime_services_path}/common/include",
"${cli_tool_framework_path}/interfaces/cli_tool/include",
]
sources = [
"cli_tool_mgr_scheduler_recipient_test.cpp",
"${cli_tool_framework_path}/interfaces/cli_tool/src/cli_event_reply_manager.cpp",
"${cli_tool_framework_path}/interfaces/cli_tool/src/cli_session_info.cpp",
"${cli_tool_framework_path}/interfaces/cli_tool/src/cli_session_subscription_manager.cpp",
"${cli_tool_framework_path}/interfaces/cli_tool/src/cli_tool_event.cpp",
"${cli_tool_framework_path}/interfaces/cli_tool/src/cli_tool_mgr_scheduler_recipient.cpp",
"${cli_tool_framework_path}/interfaces/cli_tool/src/exec_result.cpp",
]
deps = [ "${cli_tool_framework_path}/interfaces/cli_tool:cli_tool_client" ]
external_deps = [
"ability_base:want",
"c_utils:utils",
"googletest:gmock_main",
"googletest:gtest_main",
"hilog:libhilog",
"ipc:ipc_core",
]
}
group("unittest") {
testonly = true
deps = [ ":cli_tool_mgr_scheduler_recipient_test" ]
}
@@ -0,0 +1,84 @@
/*
* Copyright (c) 2026 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 <gtest/gtest.h>
#include <optional>
#include <string>
#include "cli_event_reply_manager.h"
#include "cli_session_subscription_manager.h"
#include "cli_tool_mgr_scheduler_recipient.h"
using namespace testing::ext;
namespace OHOS {
namespace CliTool {
namespace {
constexpr int32_t ERR_OK = 0;
constexpr int32_t TEST_RESULT_CODE = 1001;
}
class CliToolMgrSchedulerRecipientTest : public testing::Test {
public:
void TearDown() override
{
CliEventReplyManager::GetInstance().ClearAllEvent();
CliSessionSubscriptionManager::GetInstance().ClearAllSubscriptions();
}
};
/**
* @tc.name: CliToolManagerSchedulerRecipient_0100
* @tc.desc: Test scheduler recipient forwards events to managers
* @tc.type: FUNC
*/
HWTEST_F(CliToolMgrSchedulerRecipientTest, CliToolManagerSchedulerRecipient_0100, TestSize.Level1)
{
CliToolManagerSchedulerRecipient recipient;
int32_t replyCode = 0;
std::string eventId = CliEventReplyManager::GetInstance().AddEventReplyCallback("reply-",
[&](const CliEventReplyResult &result) {
replyCode = result.code;
});
CliEventReplyManager::GetInstance().ActivateEventReplyCallback(eventId);
EXPECT_EQ(recipient.SchedulerInputReplyEvent(eventId, TEST_RESULT_CODE), ERR_OK);
EXPECT_EQ(replyCode, TEST_RESULT_CODE);
std::optional<CliSessionInfo> replySession;
std::string execEventId = CliEventReplyManager::GetInstance().AddEventReplyCallback("exec-",
[&](const CliEventReplyResult &result) {
replySession = result.sessionInfo;
});
CliEventReplyManager::GetInstance().ActivateEventReplyCallback(execEventId);
CliSessionInfo session;
session.sessionId = "scheduler-session";
EXPECT_EQ(recipient.SchedulerExecToolReplyEvent(execEventId, ERR_OK, session), ERR_OK);
ASSERT_TRUE(replySession.has_value());
EXPECT_EQ(replySession->sessionId, "scheduler-session");
int32_t sessionEventCount = 0;
CliToolEvent event;
event.type = "stdout";
std::string subscriptionId = CliSessionSubscriptionManager::GetInstance().AddProvisionalSubscription("session",
[&](const std::string &, const std::string &, const CliToolEvent &) {
sessionEventCount++;
});
CliSessionSubscriptionManager::GetInstance().ActivateSubscription(subscriptionId);
EXPECT_EQ(recipient.SchedulerSessionEvent("session", subscriptionId, event), ERR_OK);
EXPECT_EQ(sessionEventCount, 1);
}
} // namespace CliTool
} // namespace OHOS
@@ -20,35 +20,46 @@ ohos_unittest("cli_tool_mgr_service_test") {
module_out_path = module_output_path
include_dirs = [
"${ability_runtime_innerkits_path}/app_manager/include/appmgr",
"${ability_runtime_path}/interfaces/kits/native/appkit/ability_bundle_manager_helper",
"${cli_tool_framework_path}/services/climgr/include",
"${cli_tool_framework_path}/interfaces/cli_tool/include",
"${ability_runtime_path}/test/unittest/cli_tool_mgr/cli_tool_mgr_service_test",
"${cli_tool_framework_path}/interfaces/cli_tool/include",
"${cli_tool_framework_path}/services/climgr/include",
"${cli_tool_framework_path}/services/common/include",
"${cli_tool_framework_path}/test/unittest/common_mock/climgr_data/include",
]
sources = [
"cli_tool_mgr_service_test.cpp",
"${cli_tool_framework_path}/services/climgr/src/cli_tool_app_state_observer.cpp",
"${cli_tool_framework_path}/services/climgr/src/cli_tool_manager_service.cpp",
"${cli_tool_framework_path}/services/climgr/src/cli_tool_data_manager.cpp",
"${cli_tool_framework_path}/services/climgr/src/event_dispatcher.cpp",
"${cli_tool_framework_path}/services/climgr/src/io_monitor.cpp",
"${cli_tool_framework_path}/services/climgr/src/permission_query_util.cpp",
"${cli_tool_framework_path}/services/climgr/src/process_manager.cpp",
"${cli_tool_framework_path}/services/climgr/src/session_record.cpp",
"${cli_tool_framework_path}/services/climgr/src/tool_util.cpp",
"${cli_tool_framework_path}/services/climgr/src/event_dispatcher.cpp",
"${cli_tool_framework_path}/services/climgr/src/io_monitor.cpp",
"${cli_tool_framework_path}/services/common/src/ccm_util.cpp",
"${cli_tool_framework_path}/services/common/src/permission_util.cpp",
"${cli_tool_framework_path}/test/unittest/common_mock/climgr_data/src/cli_tool_data_manager_mock.cpp",
]
cflags = []
if (target_cpu == "arm") {
cflags += [ "-BINDER_IPC_32BIT" ]
cflags += [ "-DBINDER_IPC_32BIT" ]
}
deps = [
"${ability_runtime_innerkits_path}/ability_manager:ability_manager",
"${ability_runtime_innerkits_path}/app_manager:app_manager",
"${ability_runtime_native_path}/appkit:appkit_manager_helper",
"${cli_tool_framework_path}/interfaces/cli_tool:cli_tool_client",
]
external_deps = [
"access_token:libaccesstoken_sdk",
"access_token:libnativetoken",
"access_token:libtoken_setproc",
"access_token:libtokenid_sdk",
"bundle_framework:appexecfwk_base",
"bundle_framework:appexecfwk_core",
@@ -57,11 +68,12 @@ ohos_unittest("cli_tool_mgr_service_test") {
"googletest:gmock_main",
"googletest:gtest_main",
"hilog:libhilog",
"init:libbegetutil",
"ipc:ipc_core",
"json:nlohmann_json_static",
"kv_store:distributeddata_inner",
"samgr:samgr_proxy",
"safwk:system_ability_fwk",
"samgr:samgr_proxy",
]
}
@@ -0,0 +1,914 @@
/*
* Copyright (c) 2026 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 <atomic>
#include <chrono>
#include <condition_variable>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <unistd.h>
#include <vector>
#define protected public
#define private public
#include "cli_tool_manager_service.h"
#undef private
#undef protected
#include "cli_error_code.h"
#include "cli_tool_app_state_observer.h"
#include "ccm_util.h"
#include "event_dispatcher.h"
#include "exec_options.h"
#include "nativetoken_kit.h"
#include "token_setproc.h"
#include "tool_info.h"
#include "tool_util.h"
using namespace testing::ext;
using namespace OHOS::CliTool;
namespace OHOS {
namespace CliTool {
namespace {
const char *CLI_TOOL_PERMS[] = {
"ohos.permission.EXEC_CLI_TOOL",
};
bool IsPermissionGateResult(int32_t result)
{
return result == ERR_NOT_SYSTEM_APP || result == ERR_PERMISSION_DENIED;
}
}
class CliToolManagerServiceTest : public testing::Test {
public:
static void SetUpTestCase(void);
static void TearDownTestCase(void);
void SetUp();
void TearDown();
void RegisterTestTool(const std::string& name, const std::string& schema);
sptr<CliToolManagerService> service_;
};
void CliToolManagerServiceTest::SetUpTestCase(void)
{
NativeTokenInfoParams infoInstance = {
.dcapsNum = 0,
.permsNum = static_cast<int32_t>(sizeof(CLI_TOOL_PERMS) / sizeof(CLI_TOOL_PERMS[0])),
.aclsNum = 0,
.dcaps = nullptr,
.perms = CLI_TOOL_PERMS,
.acls = nullptr,
.aplStr = "system_core",
};
infoInstance.processName = "CliToolManagerServiceTest";
auto tokenId = GetAccessTokenId(&infoInstance);
SetSelfTokenID(tokenId);
}
void CliToolManagerServiceTest::TearDownTestCase(void)
{
// Cleanup test environment
}
void CliToolManagerServiceTest::SetUp()
{
service_ = CliToolManagerService::GetInstance();
service_->interfaceCalledCount_.store(0);
EventDispatcher::GetInstance().ClearAll();
std::lock_guard<ffrt::mutex> guard(service_->sessionsMutex_);
service_->sessionRecords_.clear();
}
void CliToolManagerServiceTest::TearDown()
{
service_->interfaceCalledCount_.store(0);
EventDispatcher::GetInstance().ClearAll();
std::lock_guard<ffrt::mutex> guard(service_->sessionsMutex_);
service_->sessionRecords_.clear();
}
void CliToolManagerServiceTest::RegisterTestTool(const std::string& name, const std::string& schema)
{
ToolInfo tool;
tool.name = name;
tool.description = "Test tool: " + name;
tool.executablePath = "/system/bin/" + name;
tool.inputSchema = schema;
CliToolDataManager::GetInstance().RegisterTool(tool);
}
/**
* @tc.name: CliToolManagerService_GetInstance_0100
* @tc.desc: Test GetInstance returns singleton instance
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, GetInstance_0100, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_GetInstance_0100 start";
auto instance1 = CliToolManagerService::GetInstance();
auto instance2 = CliToolManagerService::GetInstance();
EXPECT_EQ(instance1.GetRefPtr(), instance2.GetRefPtr());
GTEST_LOG_(INFO) << "CliToolManagerService_GetInstance_0100 end";
}
/**
* @tc.name: CliToolManagerService_OnIdle_0100
* @tc.desc: Test OnIdle blocks unload when IPC or session is active
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, OnIdle_0100, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_OnIdle_0100 start";
SystemAbilityOnDemandReason idleReason;
EXPECT_EQ(service_->OnIdle(idleReason), 0);
service_->interfaceCalledCount_.store(1);
EXPECT_EQ(service_->OnIdle(idleReason), -1);
service_->interfaceCalledCount_.store(0);
auto record = std::make_shared<SessionRecord>();
record->sessionId = "test_session";
service_->AddSessionRecord(record);
EXPECT_EQ(service_->OnIdle(idleReason), -1);
GTEST_LOG_(INFO) << "CliToolManagerService_OnIdle_0100 end";
}
/**
* @tc.name: CliToolManagerService_IOMonitorSendMessage_0100
* @tc.desc: Test IOMonitor serializes high volume input writes without random pipe backpressure failure
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, IOMonitorSendMessage_0100, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_IOMonitorSendMessage_0100 start";
constexpr int32_t sendCount = 1200;
constexpr const char* sessionId = "test_session";
const std::string message(128, 'x');
int stdinPipe[2] = {-1, -1};
ASSERT_EQ(pipe(stdinPipe), 0);
auto monitor = IOMonitor::Create();
ASSERT_NE(monitor, nullptr);
ASSERT_TRUE(monitor->Start());
ASSERT_TRUE(monitor->RegisterSession(sessionId, -1, -1, stdinPipe[1]));
std::atomic<int32_t> replyCount = 0;
std::atomic<int32_t> failedCount = 0;
std::mutex replyMutex;
std::condition_variable replyCv;
monitor->SetInputReplyCallback([&](const std::string &, const std::string &, bool result) {
if (!result) {
failedCount.fetch_add(1);
}
if (replyCount.fetch_add(1) + 1 == sendCount) {
std::lock_guard<std::mutex> lock(replyMutex);
replyCv.notify_one();
}
});
std::atomic<size_t> readBytes = 0;
std::thread reader([&]() {
char buffer[256] = {};
const size_t expectedBytes = sendCount * message.size();
while (readBytes.load() < expectedBytes) {
ssize_t readResult = read(stdinPipe[0], buffer, sizeof(buffer));
if (readResult > 0) {
readBytes.fetch_add(static_cast<size_t>(readResult));
} else {
break;
}
}
});
for (int32_t i = 0; i < sendCount; ++i) {
monitor->SendMessage(sessionId, message, "event_" + std::to_string(i));
}
std::unique_lock<std::mutex> lock(replyMutex);
EXPECT_TRUE(replyCv.wait_for(lock, std::chrono::seconds(5), [&]() {
return replyCount.load() == sendCount;
}));
EXPECT_EQ(failedCount.load(), 0);
monitor->UnregisterSession(sessionId);
monitor->Stop();
if (reader.joinable()) {
reader.join();
}
close(stdinPipe[0]);
GTEST_LOG_(INFO) << "CliToolManagerService_IOMonitorSendMessage_0100 end";
}
/**
* @tc.name: CliToolManagerService_SubscribeSession_0100
* @tc.desc: Test SubscribeSession rejects non-running sessions
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, SubscribeSession_0100, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_SubscribeSession_0100 start";
auto runningRecord = std::make_shared<SessionRecord>();
runningRecord->sessionId = "running_session";
service_->AddSessionRecord(runningRecord);
int32_t runningRet = service_->SubscribeSession(runningRecord->sessionId, "running_subscription");
EXPECT_TRUE(runningRet == ERR_NO_INIT || runningRet == ERR_NOT_SYSTEM_APP || runningRet == ERR_PERMISSION_DENIED);
if (runningRet != ERR_NO_INIT) {
GTEST_LOG_(INFO) << "CliToolManagerService_SubscribeSession_0100 skipped status gate checks";
return;
}
auto completedRecord = std::make_shared<SessionRecord>();
completedRecord->sessionId = "completed_session";
completedRecord->SetTerminalResult(0, 0);
completedRecord->MarkStdoutClosed();
completedRecord->MarkStderrClosed();
service_->AddSessionRecord(completedRecord);
EXPECT_EQ(service_->SubscribeSession(completedRecord->sessionId, "completed_subscription"),
ERR_CLI_SESSION_NOT_FOUND);
auto failedRecord = std::make_shared<SessionRecord>();
failedRecord->sessionId = "failed_session";
failedRecord->SetTerminalResult(1, 0);
failedRecord->MarkStdoutClosed();
failedRecord->MarkStderrClosed();
service_->AddSessionRecord(failedRecord);
EXPECT_EQ(service_->SubscribeSession(failedRecord->sessionId, "failed_subscription"), ERR_CLI_SESSION_NOT_FOUND);
GTEST_LOG_(INFO) << "CliToolManagerService_SubscribeSession_0100 end";
}
/**
* @tc.name: CliToolManagerService_ExecTool_0100
* @tc.desc: Test ExecTool when session limit is exceeded
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, ExecTool_0100, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_ExecTool_0100 start";
auto cliQuantity = CcmUtil::GetInstance().GetCliConcurrencyLimit();
for (int32_t i = 0; i < cliQuantity; ++i) {
auto record = std::make_shared<SessionRecord>();
record->sessionId = "test_session_" + std::to_string(i);
service_->AddSessionRecord(record);
}
int32_t result = service_->ValidateSessionLimit();
EXPECT_EQ(result, ERR_SESSION_LIMIT_EXCEEDED);
EXPECT_EQ(service_->sessionRecords_.size(), static_cast<size_t>(cliQuantity));
GTEST_LOG_(INFO) << "CliToolManagerService_ExecTool_0100 end";
}
/**
* @tc.name: CliToolManagerService_ExecTool_0200
* @tc.desc: Test ExecTool when tool does not exist
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, ExecTool_0200, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_ExecTool_0200 start";
ExecToolParam param;
param.toolName = "non_existent_tool";
param.subcommand = "";
param.challenge = "test_challenge";
ToolInfo toolInfo;
std::string sandboxConfig;
std::string bundleName;
int32_t result = service_->ValidateAndPrepareTool(param, 0, toolInfo, sandboxConfig, bundleName);
EXPECT_TRUE(result == ERR_TOOL_NOT_EXIST || result == ERR_NO_INIT);
GTEST_LOG_(INFO) << "CliToolManagerService_ExecTool_0200 end";
}
/**
* @tc.name: CliToolManagerService_ExecTool_0300
* @tc.desc: Test ExecTool with empty tool name
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, ExecTool_0300, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_ExecTool_0300 start";
ExecToolParam param;
param.toolName = "";
param.subcommand = "";
param.challenge = "test_challenge";
ToolInfo toolInfo;
std::string sandboxConfig;
std::string bundleName;
int32_t result = service_->ValidateAndPrepareTool(param, 0, toolInfo, sandboxConfig, bundleName);
EXPECT_TRUE(result == ERR_TOOL_NOT_EXIST || result == ERR_NO_INIT);
GTEST_LOG_(INFO) << "CliToolManagerService_ExecTool_0300 end";
}
/**
* @tc.name: CliToolManagerService_ExecTool_0500
* @tc.desc: Test ExecTool with invalid subcommand
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, ExecTool_0500, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_ExecTool_0500 start";
ToolInfo toolInfo;
toolInfo.name = "test_tool_subcmd";
toolInfo.description = "Test tool with subcommand";
toolInfo.executablePath = "/system/bin/test_tool_subcmd";
toolInfo.hasSubCommand = true;
SubCommandInfo subCommandInfo;
subCommandInfo.description = "Build subcommand";
toolInfo.subcommands["build"] = subCommandInfo;
ExecToolParam param;
param.toolName = "test_tool_subcmd";
param.subcommand = "invalid_subcmd";
param.challenge = "test_challenge";
int32_t result = ToolUtil::ValidateProperties(toolInfo, param, 0);
EXPECT_EQ(result, ERR_TOOL_NOT_EXIST);
GTEST_LOG_(INFO) << "CliToolManagerService_ExecTool_0500 end";
}
// ==================== Permission Validation Tests ====================
/**
* @tc.name: CliToolManagerService_GetAllToolInfos_Permission_0100
* @tc.desc: Test GetAllToolInfos permission check - should require system app and permission
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, GetAllToolInfos_Permission_0100, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_GetAllToolInfos_Permission_0100 start";
// Note: In unit test environment, the caller is typically a system app with permissions
// This test verifies the method completes successfully when permissions are granted
ToolsRawData toolsRawData;
int32_t result = service_->GetAllToolInfos(toolsRawData);
// In test environment, should succeed or return appropriate error
EXPECT_TRUE(result == ERR_OK || result == ERR_NOT_SYSTEM_APP || result == ERR_PERMISSION_DENIED);
GTEST_LOG_(INFO) << "CliToolManagerService_GetAllToolInfos_Permission_0100 end";
}
/**
* @tc.name: CliToolManagerService_GetAllToolSummaries_Permission_0100
* @tc.desc: Test GetAllToolSummaries permission check - should require system app and permission
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, GetAllToolSummaries_Permission_0100, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_GetAllToolSummaries_Permission_0100 start";
std::vector<ToolSummary> summaries;
int32_t result = service_->GetAllToolSummaries(summaries);
// In test environment, should succeed or return appropriate error
EXPECT_TRUE(result == ERR_OK || result == ERR_NOT_SYSTEM_APP || result == ERR_PERMISSION_DENIED);
GTEST_LOG_(INFO) << "CliToolManagerService_GetAllToolSummaries_Permission_0100 end";
}
/**
* @tc.name: CliToolManagerService_GetToolInfoByName_Permission_0100
* @tc.desc: Test GetToolInfoByName permission check - should require system app and permission
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, GetToolInfoByName_Permission_0100, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_GetToolInfoByName_Permission_0100 start";
ToolInfo tool;
int32_t result = service_->GetToolInfoByName("test_tool", tool);
// In test environment, should succeed or return appropriate error
// ERR_NO_INIT (-1) indicates data manager not initialized
EXPECT_TRUE(result == ERR_OK || result == ERR_NOT_SYSTEM_APP || result == ERR_PERMISSION_DENIED ||
result == ERR_NO_INIT);
GTEST_LOG_(INFO) << "CliToolManagerService_GetToolInfoByName_Permission_0100 end";
}
/**
* @tc.name: CliToolManagerService_QueryPermission_Required_0100
* @tc.desc: Test that query methods require ohos.permission.QUERY_CLI_TOOL permission
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, QueryPermission_Required_0100, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_QueryPermission_Required_0100 start";
// This test documents that the following methods require:
// 1. Caller must be a system app
// 2. Caller must have ohos.permission.QUERY_CLI_TOOL permission
//
// Methods covered:
// - GetAllToolInfos
// - GetAllToolSummaries
// - GetToolInfoByName
//
// In production, if caller is not system app: returns ERR_NOT_SYSTEM_APP
// If caller lacks permission: returns ERR_PERMISSION_DENIED
GTEST_LOG_(INFO) << "CliToolManagerService_QueryPermission_Required_0100 end";
}
/**
* @tc.name: CliToolManagerService_AppStateObserver_0100
* @tc.desc: Test app state observer exposes a valid remote object
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, AppStateObserver_0100, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_AppStateObserver_0100 start";
sptr<CliToolAppStateObserver> observer = new CliToolAppStateObserver("test.bundle", nullptr);
EXPECT_NE(observer->AsObject(), nullptr);
GTEST_LOG_(INFO) << "CliToolManagerService_AppStateObserver_0100 end";
}
/**
* @tc.name: CliToolManagerService_AppStateObserver_0200
* @tc.desc: Test app state observer forwards process died callback
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, AppStateObserver_0200, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_AppStateObserver_0200 start";
std::string diedBundleName;
pid_t diedPid = 0;
sptr<CliToolAppStateObserver> observer = new CliToolAppStateObserver(
"test.bundle", [&diedBundleName, &diedPid](const std::string &bundleName, pid_t pid) {
diedBundleName = bundleName;
diedPid = pid;
});
AppExecFwk::ProcessData processData;
processData.pid = 1001;
observer->OnProcessDied(processData);
EXPECT_EQ(diedBundleName, "test.bundle");
EXPECT_EQ(diedPid, 1001);
GTEST_LOG_(INFO) << "CliToolManagerService_AppStateObserver_0200 end";
}
/**
* @tc.name: CliToolManagerService_SessionRecord_0100
* @tc.desc: Test GetSessionRecord removes null leak entries and RemoveSessionRecord erases records
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, SessionRecord_0100, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_SessionRecord_0100 start";
{
std::lock_guard<ffrt::mutex> guard(service_->sessionsMutex_);
service_->sessionRecords_["leak_session"] = nullptr;
}
EXPECT_EQ(service_->GetSessionRecord("leak_session"), nullptr);
EXPECT_EQ(service_->sessionRecords_.find("leak_session"), service_->sessionRecords_.end());
auto record = std::make_shared<SessionRecord>();
record->sessionId = "normal_session";
service_->AddSessionRecord(record);
EXPECT_EQ(service_->GetSessionRecord("normal_session"), record);
service_->RemoveSessionRecord("normal_session");
EXPECT_EQ(service_->GetSessionRecord("normal_session"), nullptr);
GTEST_LOG_(INFO) << "CliToolManagerService_SessionRecord_0100 end";
}
/**
* @tc.name: CliToolManagerService_CreateSessionRecord_0100
* @tc.desc: Test CreateSessionRecord initializes session fields from ExecToolParam
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, CreateSessionRecord_0100, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_CreateSessionRecord_0100 start";
ExecToolParam param;
param.toolName = "create_tool";
param.options.background = false;
param.options.timeout = 12;
auto record = service_->CreateSessionRecord(param, "event-id");
ASSERT_NE(record, nullptr);
EXPECT_EQ(record->toolName, "create_tool");
EXPECT_TRUE(record->sessionId.find("create_tool_") == 0);
EXPECT_EQ(record->timeoutMs, 12 * 1000);
EXPECT_EQ(record->eventId, "event-id");
EXPECT_EQ(record->GetState(), SessionState::RUNNING);
EXPECT_FALSE(record->Background());
EXPECT_GT(record->startTime, 0);
GTEST_LOG_(INFO) << "CliToolManagerService_CreateSessionRecord_0100 end";
}
/**
* @tc.name: CliToolManagerService_HandleProcessYieldTimeout_0100
* @tc.desc: Test yield timeout missing-session and foreground-to-background branches
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, HandleProcessYieldTimeout_0100, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_HandleProcessYieldTimeout_0100 start";
service_->HandleProcessYieldTimeout("missing_session");
auto record = std::make_shared<SessionRecord>();
record->sessionId = "yield_session";
record->eventId = "yield_event";
record->SetBackground(false);
service_->AddSessionRecord(record);
service_->HandleProcessYieldTimeout(record->sessionId);
EXPECT_TRUE(record->Background());
EXPECT_NE(service_->GetSessionRecord(record->sessionId), nullptr);
GTEST_LOG_(INFO) << "CliToolManagerService_HandleProcessYieldTimeout_0100 end";
}
/**
* @tc.name: CliToolManagerService_HandleProcessTimeout_0100
* @tc.desc: Test process timeout marks CLI session timed out and cancelling
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, HandleProcessTimeout_0100, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_HandleProcessTimeout_0100 start";
service_->HandleProcessTimeout("missing_session");
auto record = std::make_shared<SessionRecord>();
record->sessionId = "timeout_session";
record->eventId = "timeout_event";
record->processId = 999999;
record->SetBackground(true);
service_->AddSessionRecord(record);
service_->HandleProcessTimeout(record->sessionId);
EXPECT_TRUE(record->TimedOut());
EXPECT_EQ(record->GetState(), SessionState::CANCELLING);
EXPECT_TRUE(record->Background());
EXPECT_NE(service_->GetSessionRecord(record->sessionId), nullptr);
GTEST_LOG_(INFO) << "CliToolManagerService_HandleProcessTimeout_0100 end";
}
/**
* @tc.name: CliToolManagerService_HandleSkillSessionTimeout_0100
* @tc.desc: Test skill timeout removes skill session and handles missing session
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, HandleSkillSessionTimeout_0100, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_HandleSkillSessionTimeout_0100 start";
service_->HandleSkillSessionTimeout("missing_session");
auto record = std::make_shared<SessionRecord>();
record->sessionId = "skill_timeout_session";
record->eventId = "skill_timeout_event";
record->sessionType = SessionType::SKILL;
record->SetBackground(true);
service_->AddSessionRecord(record);
service_->HandleProcessTimeout(record->sessionId);
EXPECT_TRUE(record->TimedOut());
EXPECT_EQ(record->GetState(), SessionState::FAILED);
EXPECT_EQ(service_->GetSessionRecord(record->sessionId), nullptr);
GTEST_LOG_(INFO) << "CliToolManagerService_HandleSkillSessionTimeout_0100 end";
}
/**
* @tc.name: CliToolManagerService_HandleOutputClosed_0100
* @tc.desc: Test output close branches for missing, stdout and stderr paths
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, HandleOutputClosed_0100, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_HandleOutputClosed_0100 start";
service_->HandleOutputClosed("missing_session", true);
auto record = std::make_shared<SessionRecord>();
record->sessionId = "output_session";
service_->AddSessionRecord(record);
service_->HandleOutputClosed(record->sessionId, true);
EXPECT_FALSE(record->OutputDrained());
service_->HandleOutputClosed(record->sessionId, false);
EXPECT_TRUE(record->OutputDrained());
EXPECT_NE(service_->GetSessionRecord(record->sessionId), nullptr);
GTEST_LOG_(INFO) << "CliToolManagerService_HandleOutputClosed_0100 end";
}
/**
* @tc.name: CliToolManagerService_FinalizeBackgroundSession_0100
* @tc.desc: Test finalize background session null, success and duplicate cleanup branches
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, FinalizeBackgroundSession_0100, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_FinalizeBackgroundSession_0100 start";
service_->FinalizeBackgroundSession(nullptr);
auto record = std::make_shared<SessionRecord>();
record->sessionId = "finalize_session";
record->eventId = "finalize_event";
record->SetBackground(true);
record->MarkStdoutClosed();
record->MarkStderrClosed();
record->SetTerminalResult(0, 0);
service_->AddSessionRecord(record);
service_->FinalizeBackgroundSession(record);
EXPECT_EQ(service_->GetSessionRecord(record->sessionId), nullptr);
service_->FinalizeBackgroundSession(record);
EXPECT_EQ(service_->GetSessionRecord(record->sessionId), nullptr);
GTEST_LOG_(INFO) << "CliToolManagerService_FinalizeBackgroundSession_0100 end";
}
/**
* @tc.name: CliToolManagerService_RegisterSessionWithMonitors_0100
* @tc.desc: Test monitor registration failure when ioMonitor is null
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, RegisterSessionWithMonitors_0100, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_RegisterSessionWithMonitors_0100 start";
auto oldMonitor = service_->ioMonitor_;
service_->ioMonitor_ = nullptr;
auto record = std::make_shared<SessionRecord>();
record->sessionId = "monitor_session";
record->stdoutPipe[0] = -1;
record->stderrPipe[0] = -1;
record->stdinPipe[1] = -1;
ExecToolParam param;
EXPECT_FALSE(service_->RegisterSessionWithMonitors(record, param));
service_->ioMonitor_ = oldMonitor;
GTEST_LOG_(INFO) << "CliToolManagerService_RegisterSessionWithMonitors_0100 end";
}
/**
* @tc.name: CliToolManagerService_HandleSkillSessionComplete_0100
* @tc.desc: Test skill completion missing, duplicate and cleanup branches
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, HandleSkillSessionComplete_0100, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_HandleSkillSessionComplete_0100 start";
CliSessionInfo session;
session.sessionId = "missing_skill_session";
service_->HandleSkillSessionComplete("missing_skill_session", 0, "event", ERR_OK, session);
auto record = std::make_shared<SessionRecord>();
record->sessionId = "skill_complete_session";
record->eventId = "skill_complete_event";
record->sessionType = SessionType::SKILL;
record->SetBackground(true);
service_->AddSessionRecord(record);
session.sessionId = record->sessionId;
service_->HandleSkillSessionComplete(record->sessionId, 0, record->eventId, ERR_OK, session);
EXPECT_EQ(service_->GetSessionRecord(record->sessionId), nullptr);
service_->HandleSkillSessionComplete(record->sessionId, 0, record->eventId, ERR_OK, session);
EXPECT_EQ(service_->GetSessionRecord(record->sessionId), nullptr);
GTEST_LOG_(INFO) << "CliToolManagerService_HandleSkillSessionComplete_0100 end";
}
/**
* @tc.name: CliToolManagerService_WaitPid_0100
* @tc.desc: Test WaitPid ignores unknown pid and finalizes drained matching record
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, WaitPid_0100, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_WaitPid_0100 start";
service_->WaitPid(12345, 0, 0);
auto record = std::make_shared<SessionRecord>();
record->sessionId = "waitpid_session";
record->processId = 23456;
record->MarkStdoutClosed();
record->MarkStderrClosed();
service_->AddSessionRecord(record);
service_->WaitPid(record->processId, 0, 0);
EXPECT_TRUE(record->HasProcessExited());
EXPECT_EQ(service_->GetSessionRecord(record->sessionId), nullptr);
GTEST_LOG_(INFO) << "CliToolManagerService_WaitPid_0100 end";
}
/**
* @tc.name: CliToolManagerService_RegisterScheduler_0100
* @tc.desc: Test RegisterScheduler with null and valid scheduler
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, RegisterScheduler_0100, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_RegisterScheduler_0100 start";
EXPECT_NE(service_->RegisterScheduler(nullptr), ERR_OK);
GTEST_LOG_(INFO) << "CliToolManagerService_RegisterScheduler_0100 end";
}
/**
* @tc.name: CliToolManagerService_UnregisterScheduler_0100
* @tc.desc: Test UnregisterScheduler clears scheduler
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, UnregisterScheduler_0100, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_UnregisterScheduler_0100 start";
service_->UnregisterScheduler();
GTEST_LOG_(INFO) << "CliToolManagerService_UnregisterScheduler_0100 end";
}
/**
* @tc.name: CliToolManagerService_ClearSession_0100
* @tc.desc: Test ClearSession with missing session
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, ClearSession_0100, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_ClearSession_0100 start";
int32_t result = service_->ClearSession("nonexistent_session");
EXPECT_TRUE(result == ERR_CLI_SESSION_NOT_FOUND || IsPermissionGateResult(result));
if (IsPermissionGateResult(result)) {
GTEST_LOG_(INFO) << "CliToolManagerService_ClearSession_0100 skipped session gate checks";
return;
}
auto record = std::make_shared<SessionRecord>();
record->sessionId = "completed_session";
record->SetState(SessionState::COMPLETED);
service_->AddSessionRecord(record);
EXPECT_EQ(service_->ClearSession("completed_session"), ERR_CLI_SESSION_NOT_FOUND);
GTEST_LOG_(INFO) << "CliToolManagerService_ClearSession_0100 end";
}
/**
* @tc.name: CliToolManagerService_QuerySession_0100
* @tc.desc: Test QuerySession with missing session returns error
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, QuerySession_0100, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_QuerySession_0100 start";
CliSessionInfo session;
int32_t result = service_->QuerySession("missing_session", session);
EXPECT_TRUE(result == ERR_CLI_SESSION_NOT_FOUND || IsPermissionGateResult(result));
GTEST_LOG_(INFO) << "CliToolManagerService_QuerySession_0100 end";
}
/**
* @tc.name: CliToolManagerService_SubscribeSession_0200
* @tc.desc: Test SubscribeSession with empty args and missing session
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, SubscribeSession_0200, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_SubscribeSession_0200 start";
int32_t result = service_->SubscribeSession("", "sub1");
EXPECT_TRUE(result == ERR_INVALID_PARAM || IsPermissionGateResult(result));
if (IsPermissionGateResult(result)) {
GTEST_LOG_(INFO) << "CliToolManagerService_SubscribeSession_0200 skipped argument/session gate checks";
return;
}
EXPECT_EQ(service_->SubscribeSession("session", ""), ERR_INVALID_PARAM);
EXPECT_EQ(service_->SubscribeSession("missing", "sub1"), ERR_CLI_SESSION_NOT_FOUND);
GTEST_LOG_(INFO) << "CliToolManagerService_SubscribeSession_0200 end";
}
/**
* @tc.name: CliToolManagerService_HandleOutputDrained_0100
* @tc.desc: Test HandleOutputDrained with missing and present sessions
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, HandleOutputDrained_0100, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_HandleOutputDrained_0100 start";
service_->HandleOutputDrained("missing_session");
auto record = std::make_shared<SessionRecord>();
record->sessionId = "drained_session";
record->processId = 12345;
service_->AddSessionRecord(record);
service_->HandleOutputDrained(record->sessionId);
EXPECT_NE(service_->GetSessionRecord(record->sessionId), nullptr);
GTEST_LOG_(INFO) << "CliToolManagerService_HandleOutputDrained_0100 end";
}
/**
* @tc.name: CliToolManagerService_RegisterTool_0100
* @tc.desc: Test RegisterTool returns permission denied (system API only)
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, RegisterTool_0100, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_RegisterTool_0100 start";
ToolInfo tool;
tool.name = "ohos-test";
EXPECT_EQ(service_->RegisterTool(tool), ERR_PERMISSION_DENIED);
GTEST_LOG_(INFO) << "CliToolManagerService_RegisterTool_0100 end";
}
/**
* @tc.name: CliToolManagerService_ExecTool_0600
* @tc.desc: Test ExecTool with nonexistent tool name
* @tc.type: FUNC
*/
HWTEST_F(CliToolManagerServiceTest, ExecTool_0600, TestSize.Level1)
{
GTEST_LOG_(INFO) << "CliToolManagerService_ExecTool_0600 start";
ExecToolParam param;
param.toolName = "ohos-nonexistent_tool";
param.options.timeout = 30;
CliSessionInfo session;
int32_t result = service_->ExecTool(param, "event_exec_0600");
EXPECT_TRUE(result == ERR_TOOL_NOT_EXIST || IsPermissionGateResult(result));
GTEST_LOG_(INFO) << "CliToolManagerService_ExecTool_0600 end";
}
} // namespace CliTool
} // namespace OHOS
@@ -0,0 +1,21 @@
/*
* Copyright (c) 2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
*/
#ifndef OHOS_ABILITY_RUNTIME_CCM_UTIL_H
#define OHOS_ABILITY_RUNTIME_CCM_UTIL_H
#include <cstdint>
namespace OHOS {
namespace CliTool {
class CcmUtil {
public:
static CcmUtil &GetInstance();
int32_t GetCliConcurrencyLimit();
};
} // namespace CliTool
} // namespace OHOS
#endif // OHOS_ABILITY_RUNTIME_CCM_UTIL_H

Some files were not shown because too many files have changed in this diff Show More