1/*
2 * Copyright (c) 2022-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15#include "ability_tool_command.h"
16
17#include <cstdio>
18#include <cstring>
19#include <getopt.h>
20#include <iostream>
21#include <regex>
22
23#include "ability_manager_client.h"
24#include "bool_wrapper.h"
25#include "element_name.h"
26#include "hilog_tag_wrapper.h"
27
28using namespace OHOS::AppExecFwk;
29
30namespace OHOS {
31namespace AAFwk {
32namespace {
33const std::string ABILITY_TOOL_NAME = "ability_tool";
34const std::string ABILITY_TOOL_HELP_MSG =
35    "usage: ability_tool <command> <options>\n"
36    "ability_tool commands list:\n"
37    "  help                        list available commands\n"
38    "  start                       start ability with options\n"
39    "  stop-service                stop service with options\n"
40    "  force-stop                  force stop the process with bundle name\n"
41    "  test                        start the test framework with options\n";
42
43const std::string ABILITY_TOOL_HELP_MSG_START =
44    "usage: ability_tool start <options>\n"
45    "ability_tool start options list:\n"
46    "  --help                      list available options\n"
47    "  --device <device-id>        device Id\n"
48    "  --ability <ability-name>    ability name, mandatory\n"
49    "  --bundle <bundle-name>      bundle name, mandatory\n"
50    "  --options <key> <value>     start options, such as windowMode 102\n"
51    "  --flags <flag>              flags in a want\n"
52    "  -C                          cold start\n"
53    "  -D                          start with debug mode\n";
54
55const std::string ABILITY_TOOL_HELP_MSG_STOP_SERVICE =
56    "usage: ability_tool stop-service <options>\n"
57    "ability_tool stop-service options list:\n"
58    "  --help                      list available options\n"
59    "  --device <device-id>        device Id\n"
60    "  --ability <ability-name>    ability name, mandatory\n"
61    "  --bundle <bundle-name>      bundle name, mandatory\n";
62
63const std::string ABILITY_TOOL_HELP_MSG_FORCE_STOP =
64    "usage: ability_tool force-stop <options>\n"
65    "ability_tool force-stop options list:\n"
66    "  --help                      list available options\n"
67    "  <bundle-name>               bundle name, mandatory\n";
68
69const std::string ABILITY_TOOL_HELP_MSG_TEST =
70    "usage: ability_tool test <options>\n"
71    "ability_tool test options list:\n"
72    "  --help                              list available options\n"
73    "  --bundle <bundle-name>              bundle name, mandatory\n"
74    "  --options unittest <test-runner>    test runner need to start, mandatory\n"
75    "  --package-name <package-name>       package name, required for the FA model\n"
76    "  --module-name <module-name>         module name, required for the STAGE model\n"
77    "  --options <key> <value>             test options, such as testcase test_001\n"
78    "  --watchdog <wait-time>              max execute time for this test\n"
79    "  -D                                  test with debug mode\n";
80
81const std::string ABILITY_TOOL_HELP_MSG_NO_ABILITY_NAME_OPTION = "error: --ability <ability-name> is expected";
82const std::string ABILITY_TOOL_HELP_MSG_NO_BUNDLE_NAME_OPTION = "error: --bundle <bundle-name> is expected";
83const std::string ABILITY_TOOL_HELP_MSG_WINDOW_MODE_INVALID = "error: --options windowMode <value> with invalid param";
84const std::string ABILITY_TOOL_HELP_MSG_LACK_VALUE = "error: lack of value of key";
85const std::string ABILITY_TOOL_HELP_MSG_ONLY_NUM = "error: current option only support number";
86const std::string ABILITY_TOOL_HELP_LACK_OPTIONS = "error: lack of essential args";
87
88const std::string SHORT_OPTIONS_FOR_TEST = "hb:o:p:m:w:D";
89const struct option LONG_OPTIONS_FOR_TEST[] = {
90    {"help", no_argument, nullptr, 'h'},
91    {"bundle", required_argument, nullptr, 'b'},
92    {"options", required_argument, nullptr, 'o'},
93    {"package-name", required_argument, nullptr, 'p'},
94    {"module-name", required_argument, nullptr, 'm'},
95    {"watchdog", required_argument, nullptr, 'w'},
96    {"debug", no_argument, nullptr, 'D'},
97    {nullptr, 0, nullptr, 0},
98};
99
100const int32_t ARG_LIST_INDEX_OFFSET = 2;
101} // namespace
102
103AbilityToolCommand::AbilityToolCommand(int argc, char* argv[]) : ShellCommand(argc, argv, ABILITY_TOOL_NAME)
104{
105    for (int i = 0; i < argc_; i++) {
106        TAG_LOGI(AAFwkTag::AA_TOOL, "argv_[%{public}d]: %{public}s", i, argv_[i]);
107    }
108
109    aaShellCmd_ = std::make_shared<AbilityManagerShellCommand>(argc, argv);
110    if (aaShellCmd_.get() == nullptr) {
111        TAG_LOGE(AAFwkTag::AA_TOOL, "Get aa command failed");
112    }
113}
114
115ErrCode AbilityToolCommand::CreateCommandMap()
116{
117    commandMap_ = {
118        {"help", [this]() { return this->RunAsHelpCommand(); }},
119        {"start", [this]() { return this->RunAsStartAbility(); }},
120        {"stop-service", [this]() { return this->RunAsStopService(); }},
121        {"force-stop", [this]() { return this->RunAsForceStop(); }},
122        {"test", [this]() { return this->RunAsTestCommand(); }},
123    };
124
125    return OHOS::ERR_OK;
126}
127
128ErrCode AbilityToolCommand::CreateMessageMap()
129{
130    if (aaShellCmd_.get() == nullptr) {
131        TAG_LOGE(AAFwkTag::AA_TOOL, "null aaShellCmd_");
132        return OHOS::ERR_INVALID_VALUE;
133    }
134    return aaShellCmd_.get()->CreateMessageMap();
135}
136
137ErrCode AbilityToolCommand::init()
138{
139    return AbilityManagerClient::GetInstance()->Connect();
140}
141
142ErrCode AbilityToolCommand::RunAsHelpCommand()
143{
144    resultReceiver_.append(ABILITY_TOOL_HELP_MSG);
145    return OHOS::ERR_OK;
146}
147
148ErrCode AbilityToolCommand::RunAsStartAbility()
149{
150    Want want;
151    StartOptions startOptions;
152
153    ErrCode result = ParseStartAbilityArgsFromCmd(want, startOptions);
154    if (result != OHOS::ERR_OK) {
155        resultReceiver_.append(ABILITY_TOOL_HELP_MSG_START);
156        return result;
157    }
158
159    result = AbilityManagerClient::GetInstance()->StartAbility(want, startOptions, nullptr);
160    if (result != OHOS::ERR_OK) {
161        TAG_LOGE(AAFwkTag::AA_TOOL, "%{public}s result: %{public}d", STRING_START_ABILITY_NG.c_str(), result);
162        if (result != START_ABILITY_WAITING) {
163            resultReceiver_ = STRING_START_ABILITY_NG + "\n";
164        }
165        resultReceiver_.append(GetMessageFromCode(result));
166        return result;
167    }
168
169    TAG_LOGI(AAFwkTag::AA_TOOL, "%{public}s", STRING_START_ABILITY_OK.c_str());
170    resultReceiver_ = STRING_START_ABILITY_OK + "\n";
171    return OHOS::ERR_OK;
172}
173
174ErrCode AbilityToolCommand::RunAsStopService()
175{
176    Want want;
177
178    ErrCode result = ParseStopServiceArgsFromCmd(want);
179    if (result != OHOS::ERR_OK) {
180        resultReceiver_.append(ABILITY_TOOL_HELP_MSG_STOP_SERVICE);
181        return OHOS::ERR_INVALID_VALUE;
182    }
183
184    result = AbilityManagerClient::GetInstance()->StopServiceAbility(want);
185    if (result != OHOS::ERR_OK) {
186        TAG_LOGE(AAFwkTag::AA_TOOL, "%{public}s result: %{public}d", STRING_STOP_SERVICE_ABILITY_NG.c_str(), result);
187        resultReceiver_ = STRING_STOP_SERVICE_ABILITY_NG + "\n";
188        resultReceiver_.append(GetMessageFromCode(result));
189        return result;
190    }
191
192    TAG_LOGI(AAFwkTag::AA_TOOL, "%{public}s", STRING_STOP_SERVICE_ABILITY_OK.c_str());
193    resultReceiver_ = STRING_STOP_SERVICE_ABILITY_OK + "\n";
194    return OHOS::ERR_OK;
195}
196
197ErrCode AbilityToolCommand::RunAsForceStop()
198{
199    if (argList_.empty()) {
200        resultReceiver_.append(ABILITY_TOOL_HELP_MSG_FORCE_STOP);
201        return OHOS::ERR_INVALID_VALUE;
202    }
203
204    std::string bundleName = argList_[0];
205    ErrCode result = AbilityManagerClient::GetInstance()->KillProcess(bundleName);
206    if (result != OHOS::ERR_OK) {
207        TAG_LOGE(AAFwkTag::AA_TOOL, "%{public}s result: %{public}d", STRING_FORCE_STOP_NG.c_str(), result);
208        resultReceiver_ = STRING_FORCE_STOP_NG + "\n";
209        resultReceiver_.append(GetMessageFromCode(result));
210        return result;
211    }
212
213    TAG_LOGI(AAFwkTag::AA_TOOL, "%{public}s", STRING_FORCE_STOP_OK.c_str());
214    resultReceiver_ = STRING_FORCE_STOP_OK + "\n";
215    return OHOS::ERR_OK;
216}
217
218ErrCode AbilityToolCommand::RunAsTestCommand()
219{
220    std::map<std::string, std::string> params;
221
222    ErrCode result = ParseTestArgsFromCmd(params);
223    if (result != OHOS::ERR_OK) {
224        resultReceiver_.append(ABILITY_TOOL_HELP_MSG_TEST);
225        return result;
226    }
227
228    if (aaShellCmd_.get() == nullptr) {
229        TAG_LOGE(AAFwkTag::AA_TOOL, "null aaShellCmd_");
230        return OHOS::ERR_INVALID_VALUE;
231    }
232
233    if (!aaShellCmd_.get()->IsTestCommandIntegrity(params)) {
234        TAG_LOGE(AAFwkTag::AA_TOOL, "invalid params");
235        resultReceiver_ = ABILITY_TOOL_HELP_LACK_OPTIONS + "\n";
236        resultReceiver_.append(ABILITY_TOOL_HELP_MSG_TEST);
237        return OHOS::ERR_INVALID_VALUE;
238    }
239    return aaShellCmd_.get()->StartUserTest(params);
240}
241
242ErrCode AbilityToolCommand::ParseStartAbilityArgsFromCmd(Want& want, StartOptions& startOptions)
243{
244    std::string deviceId = "";
245    std::string bundleName = "";
246    std::string abilityName = "";
247    std::string paramName = "";
248    std::string paramValue = "";
249    std::smatch sm;
250    int32_t windowMode = AbilityWindowConfiguration::MULTI_WINDOW_DISPLAY_UNDEFINED;
251    int flags = 0;
252    bool isColdStart = false;
253    bool isDebugApp = false;
254    int option = -1;
255    int index = 0;
256    const std::string shortOptions = "hd:a:b:o:f:CD";
257    const struct option longOptions[] = {
258        {"help", no_argument, nullptr, 'h'},
259        {"device", required_argument, nullptr, 'd'},
260        {"ability", required_argument, nullptr, 'a'},
261        {"bundle", required_argument, nullptr, 'b'},
262        {"options", required_argument, nullptr, 'o'},
263        {"flags", required_argument, nullptr, 'f'},
264        {"cold-start", no_argument, nullptr, 'C'},
265        {"debug", no_argument, nullptr, 'D'},
266        {nullptr, 0, nullptr, 0},
267    };
268
269    while ((option = getopt_long(argc_, argv_, shortOptions.c_str(), longOptions, &index)) != EOF) {
270        TAG_LOGI(
271            AAFwkTag::AA_TOOL, "option: %{public}d, optopt: %{public}d, optind: %{public}d", option, optopt, optind);
272        switch (option) {
273            case 'h':
274                break;
275            case 'd':
276                deviceId = optarg;
277                break;
278            case 'a':
279                abilityName = optarg;
280                break;
281            case 'b':
282                bundleName = optarg;
283                break;
284            case 'o':
285                if (!GetKeyAndValueByOpt(optind, paramName, paramValue)) {
286                    return OHOS::ERR_INVALID_VALUE;
287                }
288                TAG_LOGD(AAFwkTag::AA_TOOL, "paramName: %{public}s, paramValue: %{public}s", paramName.c_str(),
289                    paramValue.c_str());
290                if (paramName == "windowMode" &&
291                    std::regex_match(paramValue, sm, std::regex(STRING_TEST_REGEX_INTEGER_NUMBERS))) {
292                    windowMode = std::stoi(paramValue);
293                }
294                break;
295            case 'f':
296                paramValue = optarg;
297                if (std::regex_match(paramValue, sm, std::regex(STRING_TEST_REGEX_INTEGER_NUMBERS))) {
298                    flags = std::stoi(paramValue);
299                }
300                break;
301            case 'C':
302                isColdStart = true;
303                break;
304            case 'D':
305                isDebugApp = true;
306                break;
307            default:
308                break;
309        }
310    }
311
312    // Parameter check
313    if (abilityName.size() == 0 || bundleName.size() == 0) {
314        TAG_LOGD(AAFwkTag::AA_TOOL, "'ability_tool %{public}s' without enough options", cmd_.c_str());
315        if (abilityName.size() == 0) {
316            resultReceiver_.append(ABILITY_TOOL_HELP_MSG_NO_ABILITY_NAME_OPTION + "\n");
317        }
318
319        if (bundleName.size() == 0) {
320            resultReceiver_.append(ABILITY_TOOL_HELP_MSG_NO_BUNDLE_NAME_OPTION + "\n");
321        }
322
323        return OHOS::ERR_INVALID_VALUE;
324    }
325
326    // Get Want
327    ElementName element(deviceId, bundleName, abilityName);
328    want.SetElement(element);
329
330    WantParams wantParams;
331    if (isColdStart) {
332        wantParams.SetParam("coldStart", Boolean::Box(isColdStart));
333    }
334    if (isDebugApp) {
335        wantParams.SetParam("debugApp", Boolean::Box(isDebugApp));
336    }
337    want.SetParams(wantParams);
338
339    if (flags != 0) {
340        want.AddFlags(flags);
341    }
342
343    // Get StartOptions
344    if (windowMode != AbilityWindowConfiguration::MULTI_WINDOW_DISPLAY_UNDEFINED) {
345        if (windowMode != AbilityWindowConfiguration::MULTI_WINDOW_DISPLAY_FULLSCREEN &&
346            windowMode != AbilityWindowConfiguration::MULTI_WINDOW_DISPLAY_PRIMARY &&
347            windowMode != AbilityWindowConfiguration::MULTI_WINDOW_DISPLAY_SECONDARY &&
348            windowMode != AbilityWindowConfiguration::MULTI_WINDOW_DISPLAY_FLOATING) {
349            TAG_LOGD(AAFwkTag::AA_TOOL, "'ability_tool %{public}s' %{public}s", cmd_.c_str(),
350                ABILITY_TOOL_HELP_MSG_WINDOW_MODE_INVALID.c_str());
351            resultReceiver_.append(ABILITY_TOOL_HELP_MSG_WINDOW_MODE_INVALID + "\n");
352            return OHOS::ERR_INVALID_VALUE;
353        }
354        startOptions.SetWindowMode(windowMode);
355    }
356
357    return OHOS::ERR_OK;
358}
359
360ErrCode AbilityToolCommand::ParseStopServiceArgsFromCmd(Want& want)
361{
362    std::string deviceId = "";
363    std::string abilityName = "";
364    std::string bundleName = "";
365    int option = -1;
366    int index = 0;
367    const std::string shortOptions = "hd:a:b:";
368    const struct option longOptions[] = {
369        {"help", no_argument, nullptr, 'h'},
370        {"device", required_argument, nullptr, 'd'},
371        {"ability", required_argument, nullptr, 'a'},
372        {"bundle", required_argument, nullptr, 'b'},
373        {nullptr, 0, nullptr, 0},
374    };
375
376    while ((option = getopt_long(argc_, argv_, shortOptions.c_str(), longOptions, &index)) != EOF) {
377        TAG_LOGI(
378            AAFwkTag::AA_TOOL, "option: %{public}d, optopt: %{public}d, optind: %{public}d", option, optopt, optind);
379        switch (option) {
380            case 'h':
381                break;
382            case 'd':
383                deviceId = optarg;
384                break;
385            case 'a':
386                abilityName = optarg;
387                break;
388            case 'b':
389                bundleName = optarg;
390                break;
391            default:
392                break;
393        }
394    }
395
396    if (abilityName.size() == 0 || bundleName.size() == 0) {
397        TAG_LOGI(AAFwkTag::AA_TOOL, "'ability_tool %{public}s' without enough options", cmd_.c_str());
398        if (abilityName.size() == 0) {
399            resultReceiver_.append(ABILITY_TOOL_HELP_MSG_NO_ABILITY_NAME_OPTION + "\n");
400        }
401
402        if (bundleName.size() == 0) {
403            resultReceiver_.append(ABILITY_TOOL_HELP_MSG_NO_BUNDLE_NAME_OPTION + "\n");
404        }
405
406        return OHOS::ERR_INVALID_VALUE;
407    }
408
409    ElementName element(deviceId, bundleName, abilityName);
410    want.SetElement(element);
411    return OHOS::ERR_OK;
412}
413
414ErrCode AbilityToolCommand::ParseTestArgsFromCmd(std::map<std::string, std::string>& params)
415{
416    std::string tempKey;
417    std::string paramKey;
418    std::string paramValue;
419    std::smatch sm;
420    int option = -1;
421    int index = 0;
422
423    // Parameter parse with conversion
424    while ((option = getopt_long(argc_, argv_, SHORT_OPTIONS_FOR_TEST.c_str(), LONG_OPTIONS_FOR_TEST, &index)) != EOF) {
425        TAG_LOGI(
426            AAFwkTag::AA_TOOL, "option: %{public}d, optopt: %{public}d, optind: %{public}d", option, optopt, optind);
427        switch (option) {
428            case 'h':
429                break;
430            case 'b':
431                params["-b"] = optarg;
432                break;
433            case 'o':
434                if (!GetKeyAndValueByOpt(optind, tempKey, paramValue)) {
435                    return OHOS::ERR_INVALID_VALUE;
436                }
437                TAG_LOGD(AAFwkTag::AA_TOOL, "tempKey: %{public}s, paramValue: %{public}s", tempKey.c_str(),
438                    paramValue.c_str());
439                paramKey = "-s ";
440                paramKey.append(tempKey);
441                params[paramKey] = paramValue;
442                break;
443            case 'p':
444                params["-p"] = optarg;
445                break;
446            case 'm':
447                params["-m"] = optarg;
448                break;
449            case 'w':
450                paramValue = optarg;
451                if (!(std::regex_match(paramValue, sm, std::regex(STRING_TEST_REGEX_INTEGER_NUMBERS)))) {
452                    TAG_LOGD(AAFwkTag::AA_TOOL, "'ability_tool test --watchdog %{public}s",
453                        ABILITY_TOOL_HELP_MSG_ONLY_NUM.c_str());
454                    resultReceiver_.append(ABILITY_TOOL_HELP_MSG_ONLY_NUM + "\n");
455                    return OHOS::ERR_INVALID_VALUE;
456                }
457                params["-w"] = paramValue;
458                break;
459            case 'D':
460                params["-D"] = "true";
461                break;
462            default:
463                break;
464        }
465    }
466
467    return OHOS::ERR_OK;
468}
469
470bool AbilityToolCommand::GetKeyAndValueByOpt(int optind, std::string& key, std::string& value)
471{
472    int argListIndex = optind - ARG_LIST_INDEX_OFFSET;
473    if (argListIndex < 1) {
474        return false;
475    }
476
477    bool isOption = (argList_[argListIndex - 1] == "-o" || argList_[argListIndex - 1] == "--options") ? true : false;
478    int keyIndex = isOption ? argListIndex : argListIndex - 1;
479    int valueIndex = isOption ? argListIndex + 1 : argListIndex;
480    if (keyIndex >= static_cast<int>(argList_.size()) || keyIndex < 0 ||
481        valueIndex >= static_cast<int>(argList_.size()) || valueIndex < 0) {
482        TAG_LOGD(AAFwkTag::AA_TOOL, "'ability_tool %{public}s' %{public}s", cmd_.c_str(),
483            ABILITY_TOOL_HELP_MSG_LACK_VALUE.c_str());
484        resultReceiver_.append(ABILITY_TOOL_HELP_MSG_LACK_VALUE + "\n");
485        return false;
486    }
487
488    key = argList_[keyIndex];
489    value = argList_[valueIndex];
490    return true;
491}
492} // namespace AAFwk
493} // namespace OHOS
494