* Copyright (c) 2021-2023 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 "account_command.h"
#include <fstream>
#include <getopt.h>
#include <sys/stat.h>
#include "account_log_wrapper.h"
#include "singleton.h"
#include "string_ex.h"
namespace OHOS {
namespace AccountSA {
namespace {
const char SHORT_OPTIONS[] = "hn:t:i:s:d:p:c:ea";
const struct option LONG_OPTIONS[] = {
{"help", no_argument, nullptr, 'h'},
{"name", required_argument, nullptr, 'n'},
{"type", required_argument, nullptr, 't'},
{"id", required_argument, nullptr, 'i'},
{"displayId", optional_argument, nullptr, 'd'},
{"shortName", optional_argument, nullptr, 's'},
{"disallowedPreinstalledBundles", optional_argument, nullptr, 'd'},
{"allowedPreinstalledBundles", optional_argument, nullptr, 'p'},
{"constraint", required_argument, nullptr, 'c'},
{"enable", no_argument, nullptr, 'e'},
{"all", no_argument, nullptr, 'a'},
{nullptr, no_argument, nullptr, no_argument}
};
static const char DEACTIVATE_COMMAND[] = "deactivate";
static const char DELETE_COMMAND[] = "delete";
static const char SWITCH_COMMAND[] = "switch";
static const char DUMP_COMMAND[] = "dump";
static const char SET_COMMAND[] = "set";
static const char CREATE_COMMAND[] = "create";
static constexpr int MIN_ARGUMENT_NUMBER = 2;
static constexpr int MAX_ARGUMENT_NUMBER = 4096;
static constexpr int DECIMAL_BASE = 10;
}
AccountCommand::AccountCommand(int argc, char *argv[])
{
ACCOUNT_LOGD("enter");
opterr = 0;
argc_ = argc;
argv_ = argv;
name_ = TOOL_NAME;
if (argc < MIN_ARGUMENT_NUMBER || argc > MAX_ARGUMENT_NUMBER) {
cmd_ = "help";
return;
}
cmd_ = argv[1];
for (int i = MIN_ARGUMENT_NUMBER; i < argc; i++) {
argList_.push_back(argv[i]);
}
for (int i = 0; i < argc_; i++) {
ACCOUNT_LOGD("argv_[%{public}d]: %{public}s", i, argv_[i]);
}
}
void AccountCommand::CreateCommandMap()
{
commandMap_ = {
{"help", [this] { return this->RunAsHelpCommand(); }},
{"create", [this] { return this->RunAsCreateCommand(); }},
{"delete", [this] { return this->RunAsDeleteCommand(); }},
{"dump", [this] { return this->RunAsDumpCommand(); }},
{"set", [this] { return this->RunAsSetCommand(); }},
{"switch", [this] { return this->RunAsSwitchCommand(); }},
{"deactivate", [this] { return this->RunAsDeactivateCommand(); }},
};
}
std::string AccountCommand::GetCommandErrorMsg() const
{
std::string commandErrorMsg =
name_ + ": '" + cmd_ + "' is not a valid " + name_ + " command. See '" + name_ + " help'.\n";
return commandErrorMsg;
}
std::string AccountCommand::GetUnknownOptionMsg(std::string& unknownOption) const
{
std::string result = "";
if (optind < 0 || optind > argc_) {
return result;
}
result.append("fail: unknown option");
result.append(".\n");
return result;
}
void AccountCommand::OnCommand()
{
auto respond = commandMap_[cmd_];
if (respond == nullptr) {
resultReceiver_.append(GetCommandErrorMsg());
respond = commandMap_["help"];
}
respond();
}
std::string AccountCommand::ExecCommand()
{
CreateCommandMap();
OnCommand();
return resultReceiver_;
}
ErrCode AccountCommand::RunAsHelpCommand(void)
{
ACCOUNT_LOGD("enter");
resultReceiver_.append(HELP_MSG);
return ERR_OK;
}
ErrCode AccountCommand::ParseCreateCommandOpt(std::string &name,
std::string &shortName, OsAccountType &osAccountType, CreateOsAccountOptions &options)
{
int counter = 0;
ErrCode result = ERR_OK;
while (true) {
counter++;
int option = getopt_long(argc_, argv_, SHORT_OPTIONS, LONG_OPTIONS, nullptr);
ACCOUNT_LOGD("option: %{public}d, optopt: %{public}d, optind: %{public}d", option, optopt, optind);
if (option == -1) {
if (counter == 1) {
result = RunCommandError(CREATE_COMMAND);
}
break;
}
if (option == '?') {
result = RunAsCreateCommandMissingOptionArgument();
break;
}
result = RunAsCreateCommandExistentOptionArgument(option, name, shortName, osAccountType, options);
}
return result;
}
ErrCode AccountCommand::RunAsCreateCommand(void)
{
ACCOUNT_LOGD("enter");
ErrCode result = ERR_OK;
std::string name = "";
std::string shortName = "";
OsAccountType osAccountType = END;
CreateOsAccountOptions options;
result = ParseCreateCommandOpt(name, shortName, osAccountType, options);
if (result == ERR_OK) {
if (name.size() == 0 || osAccountType == END) {
ACCOUNT_LOGD("'acm create' without enough options");
if (name.size() == 0) {
resultReceiver_.append(HELP_MSG_NO_NAME_OPTION + "\n");
}
if (osAccountType == END) {
resultReceiver_.append(HELP_MSG_NO_TYPE_OPTION + "\n");
}
result = ERR_INVALID_VALUE;
}
}
if (result != ERR_OK && result != ERR_ACCOUNT_COMMON_FILE_OPEN_FAILED) {
resultReceiver_.append(HELP_MSG_CREATE);
} else {
OsAccountInfo osAccountInfo;
if (shortName.empty()) {
shortName = name;
}
result = OsAccount::GetInstance().CreateOsAccount(name, shortName, osAccountType, osAccountInfo, options);
switch (result) {
case ERR_OK:
resultReceiver_ = STRING_CREATE_OS_ACCOUNT_OK + "\n";
break;
case ERR_OSACCOUNT_SERVICE_MANAGER_NOT_ENABLE_MULTI_ERROR:
resultReceiver_ = "create failed, reason: multiple-os-account feature not enabled\n";
break;
default:
resultReceiver_ = STRING_CREATE_OS_ACCOUNT_NG + "\n";
}
}
ACCOUNT_LOGD("result = %{public}d, name = %{public}s, type = %{public}d", result, name.c_str(), osAccountType);
return result;
}
ErrCode AccountCommand::RunAsDeleteCommand(void)
{
ErrCode result = ERR_OK;
int id = -1;
ParseCommandOpt(DELETE_COMMAND, result, id);
if (result != ERR_OK) {
resultReceiver_.append(HELP_MSG_DELETE);
} else {
result = OsAccount::GetInstance().RemoveOsAccount(id);
if (result == ERR_OK) {
resultReceiver_ = STRING_DELETE_OS_ACCOUNT_OK + "\n";
} else {
resultReceiver_ = STRING_DELETE_OS_ACCOUNT_NG + "\n";
}
}
ACCOUNT_LOGD("result = %{public}d, id = %{public}d", result, id);
return result;
}
ErrCode AccountCommand::RunAsDumpCommand(void)
{
ErrCode result = ERR_OK;
int id = -1;
ParseCommandOpt(DUMP_COMMAND, result, id);
if (result != ERR_OK) {
resultReceiver_.append(HELP_MSG_DUMP);
} else {
std::vector<std::string> state;
result = OsAccount::GetInstance().DumpState(id, state);
if (result == ERR_OK) {
for (auto info : state) {
resultReceiver_ += info + "\n";
}
} else {
resultReceiver_ = STRING_DUMP_OS_ACCOUNT_NG + "\n";
}
}
ACCOUNT_LOGD("result = %{public}d, id = %{public}d", result, id);
return result;
}
void AccountCommand::RunCommand(
int &counter, ErrCode &result, bool &enable, int &id, std::vector<std::string> &constraints)
{
while (true) {
counter++;
int option = getopt_long(argc_, argv_, SHORT_OPTIONS, LONG_OPTIONS, nullptr);
ACCOUNT_LOGD("option: %{public}d, optopt: %{public}d, optind: %{public}d", option, optopt, optind);
if (option == -1) {
if (counter == 1) {
result = RunCommandError(SET_COMMAND);
}
break;
}
if (option == '?') {
result = RunAsSetCommandMissingOptionArgument();
break;
}
result = RunAsSetCommandExistentOptionArgument(option, id, constraints, enable);
}
}
ErrCode AccountCommand::RunAsSetCommand(void)
{
ErrCode result = ERR_OK;
int counter = 0;
int id = -1;
std::vector<std::string> constraints;
bool enable = false;
RunCommand(counter, result, enable, id, constraints);
if (result == ERR_OK) {
if (id == -1 || constraints.size() == 0) {
ACCOUNT_LOGD("'acm set' without enough options");
if (id == -1) {
resultReceiver_.append(HELP_MSG_NO_ID_OPTION + "\n");
}
if (constraints.size() == 0) {
resultReceiver_.append(HELP_MSG_NO_CONSTRAINTS_OPTION + "\n");
}
result = ERR_INVALID_VALUE;
}
}
if (result != ERR_OK) {
resultReceiver_.append(HELP_MSG_SET);
} else {
result = OsAccount::GetInstance().SetOsAccountConstraints(id, constraints, enable);
if (result == ERR_OK) {
resultReceiver_ = STRING_SET_OS_ACCOUNT_CONSTRAINTS_OK + "\n";
} else {
resultReceiver_ = STRING_SET_OS_ACCOUNT_CONSTRAINTS_NG + "\n";
}
}
ACCOUNT_LOGD("result = %{public}d, id = %{public}d, enable = %{public}d", result, id, enable);
for (auto constraint : constraints) {
ACCOUNT_LOGD("constraint = %{public}s", constraint.c_str());
}
return result;
}
void AccountCommand::ParseCommandOpt(const std::string &command, ErrCode &result, int &id)
{
int counter = 0;
while (true) {
counter++;
int option = getopt_long(argc_, argv_, SHORT_OPTIONS, LONG_OPTIONS, nullptr);
ACCOUNT_LOGD("option: %{public}d, optopt: %{public}d, optind: %{public}d", option, optopt, optind);
if (option == -1) {
if (counter == 1) {
result = RunCommandError(command);
}
break;
}
if (option == '?') {
result = RunAsCommonCommandMissingOptionArgument(command);
break;
}
result = RunAsCommonCommandExistentOptionArgument(option, id);
}
}
ErrCode AccountCommand::ParseSwitchCommandOpt(int &id, unsigned long &displayId)
{
int counter = 0;
ErrCode result = ERR_OK;
while (true) {
counter++;
int option = getopt_long(argc_, argv_, SHORT_OPTIONS, LONG_OPTIONS, nullptr);
ACCOUNT_LOGD("option: %{public}d, optopt:%{public}d, optind:%{public}d", option, optopt, optind);
if (option == -1) {
if (counter == 1) {
result = RunCommandError(SWITCH_COMMAND);
}
break;
}
if (option == '?') {
result = RunAsSwitchCommandMissingOptionArgument();
break;
}
result = RunAsSwitchCommandExistentOptionArgument(option, id, displayId);
}
return result;
}
ErrCode AccountCommand::RunAsSwitchCommand(void)
{
int id = -1;
unsigned long displayId = 0;
ErrCode result = ParseSwitchCommandOpt(id, displayId);
if (result != ERR_OK) {
resultReceiver_.append(HELP_MSG_SWITCH);
} else {
result = OsAccount::GetInstance().ActivateOsAccount(id, displayId);
if (result == ERR_OK) {
resultReceiver_ = STRING_SWITCH_OS_ACCOUNT_OK + "\n";
} else {
resultReceiver_ = STRING_SWITCH_OS_ACCOUNT_NG + "\n";
}
}
ACCOUNT_LOGD("result = %{public}d, id = %{public}d", result, id);
return result;
}
ErrCode AccountCommand::RunAsDeactivateCommand(void)
{
ErrCode result = ERR_OK;
int id = -1;
ParseCommandOpt(DEACTIVATE_COMMAND, result, id);
if (result != ERR_OK) {
resultReceiver_.append(HELP_MSG_DEACTIVATE);
} else if (id != -1) {
result = OsAccount::GetInstance().DeactivateOsAccount(id);
if (result == ERR_OK) {
resultReceiver_ = STRING_DEACTIVATE_OS_ACCOUNT_OK + "\n";
} else {
resultReceiver_ = STRING_DEACTIVATE_OS_ACCOUNT_NG + "\n";
}
} else {
result = OsAccount::GetInstance().DeactivateAllOsAccounts();
if (result == ERR_OK) {
resultReceiver_ = STRING_DEACTIVATE_ALL_OS_ACCOUNTS_OK + "\n";
} else {
resultReceiver_ = STRING_DEACTIVATE_ALL_OS_ACCOUNTS_NG + "\n";
}
}
ACCOUNT_LOGD("result = %{public}d, id = %{public}d", result, id);
return result;
}
ErrCode AccountCommand::RunAsCreateCommandMissingOptionArgument(void)
{
ErrCode result = ERR_OK;
switch (optopt) {
case 'n': {
ACCOUNT_LOGD("'acm create -n' with no argument.");
resultReceiver_.append(HELP_MSG_OPTION_REQUIRES_AN_ARGUMENT + "\n");
result = ERR_INVALID_VALUE;
break;
}
case 't': {
ACCOUNT_LOGD("'acm create -t' with no argument.");
resultReceiver_.append(HELP_MSG_OPTION_REQUIRES_AN_ARGUMENT + "\n");
result = ERR_INVALID_VALUE;
break;
}
default: {
std::string unknownOption = "";
std::string unknownOptionMsg = GetUnknownOptionMsg(unknownOption);
ACCOUNT_LOGD("'acm create' with an unknown option.");
resultReceiver_.append(unknownOptionMsg);
result = ERR_INVALID_VALUE;
break;
}
}
ACCOUNT_LOGD("end, result = %{public}d", result);
return result;
}
ErrCode AccountCommand::RunAsSwitchCommandMissingOptionArgument(void)
{
ErrCode result = ERR_OK;
switch (optopt) {
case 'i': {
ACCOUNT_LOGD("'acm switch -i' with no argument.");
resultReceiver_.append(HELP_MSG_OPTION_REQUIRES_AN_ARGUMENT + "\n");
result = ERR_INVALID_VALUE;
break;
}
case 'd': {
ACCOUNT_LOGD("'acm switch -d' with no argument.");
resultReceiver_.append(HELP_MSG_OPTION_REQUIRES_AN_ARGUMENT + "\n");
result = ERR_INVALID_VALUE;
break;
}
default: {
std::string unknownOption = "";
std::string unknownOptionMsg = GetUnknownOptionMsg(unknownOption);
ACCOUNT_LOGD("'acm switch' with an unknown option.");
resultReceiver_.append(unknownOptionMsg);
result = ERR_INVALID_VALUE;
break;
}
}
ACCOUNT_LOGD("end, result = %{public}d", result);
return result;
}
ErrCode AccountCommand::RunAsCreateCommandExistentOptionArgument(const int &option, std::string &name,
std::string &shortName, OsAccountType &type, CreateOsAccountOptions &options)
{
ErrCode result = ERR_OK;
switch (option) {
case 'h': {
result = ERR_INVALID_VALUE;
break;
}
case 'n': {
name = optarg;
break;
}
case 't': {
result = AnalyzeTypeArgument(type);
break;
}
case 's': {
shortName = optarg;
break;
}
case 'd': {
result = AnalyzeListArgument(options.disallowedHapList);
break;
}
case 'p': {
std::vector<std::string> list = {};
result = AnalyzeListArgument(list);
options.allowedHapList = std::make_optional<std::vector<std::string>>(list);
break;
}
default: {
break;
}
}
ACCOUNT_LOGD("end, result = %{public}d", result);
return result;
}
ErrCode AccountCommand::RunAsCommonCommandMissingOptionArgument(const std::string &command)
{
ErrCode result = ERR_OK;
switch (optopt) {
case 'i': {
ACCOUNT_LOGD("'acm %{public}s -i' with no argument.", command.c_str());
resultReceiver_.append(HELP_MSG_OPTION_REQUIRES_AN_ARGUMENT + "\n");
result = ERR_INVALID_VALUE;
break;
}
default: {
std::string unknownOption = "";
std::string unknownOptionMsg = GetUnknownOptionMsg(unknownOption);
ACCOUNT_LOGD("'acm %{public}s' with an unknown option.", command.c_str());
resultReceiver_.append(unknownOptionMsg);
result = ERR_INVALID_VALUE;
break;
}
}
ACCOUNT_LOGD("end, result = %{public}d", result);
return result;
}
ErrCode AccountCommand::RunCommandError(const std::string &command)
{
ErrCode result = ERR_OK;
if (optind < 0 || optind >= argc_) {
ACCOUNT_LOGD("optind %{public}d invalid", optind);
return ERR_INVALID_VALUE;
}
if (strcmp(argv_[optind], cmd_.c_str()) == 0) {
ACCOUNT_LOGD("'acm %{public}s' with no option.", command.c_str());
resultReceiver_.append(HELP_MSG_NO_OPTION + "\n");
result = ERR_INVALID_VALUE;
}
ACCOUNT_LOGD("end, result = %{public}d", result);
return result;
}
ErrCode AccountCommand::RunAsSetCommandMissingOptionArgument(void)
{
ErrCode result = ERR_OK;
switch (optopt) {
case 'i': {
ACCOUNT_LOGD("'acm set -i' with no argument.");
resultReceiver_.append(HELP_MSG_OPTION_REQUIRES_AN_ARGUMENT + "\n");
result = ERR_INVALID_VALUE;
break;
}
case 'c': {
ACCOUNT_LOGD("'acm set -c' with no argument.");
resultReceiver_.append(HELP_MSG_OPTION_REQUIRES_AN_ARGUMENT + "\n");
result = ERR_INVALID_VALUE;
break;
}
default: {
std::string unknownOption = "";
std::string unknownOptionMsg = GetUnknownOptionMsg(unknownOption);
ACCOUNT_LOGD("'set dump' with an unknown option.");
resultReceiver_.append(unknownOptionMsg);
result = ERR_INVALID_VALUE;
break;
}
}
ACCOUNT_LOGD("end, result = %{public}d", result);
return result;
}
ErrCode AccountCommand::RunAsSetCommandExistentOptionArgument(
const int &option, int &id, std::vector<std::string> &constraints, bool &enable)
{
ErrCode result = ERR_OK;
switch (option) {
case 'h': {
result = ERR_INVALID_VALUE;
break;
}
case 'i': {
result = AnalyzeLocalIdArgument(id);
break;
}
case 'c': {
result = AnalyzeConstraintArgument(constraints);
break;
}
case 'e': {
enable = true;
break;
}
default: {
break;
}
}
ACCOUNT_LOGD("end, result = %{public}d, id = %{public}d", result, id);
return result;
}
ErrCode AccountCommand::RunAsCommonCommandExistentOptionArgument(const int &option, int &id)
{
ErrCode result = ERR_OK;
switch (option) {
case 'h': {
result = ERR_INVALID_VALUE;
break;
}
case 'i': {
result = AnalyzeLocalIdArgument(id);
break;
}
default: {
break;
}
}
ACCOUNT_LOGD("end, result = %{public}d, id = %{public}d", result, id);
return result;
}
ErrCode AccountCommand::RunAsSwitchCommandExistentOptionArgument(const int &option, int &id, unsigned long &displayId)
{
ErrCode result = ERR_OK;
switch (option) {
case 'h': {
result = ERR_INVALID_VALUE;
break;
}
case 'i': {
char* endptr;
long id_tmp = std::strtol(optarg, &endptr, DECIMAL_BASE);
if (*endptr != '\0') {
resultReceiver_.append(HELP_MSG_INVALID_ID_ARGUMENT + "\n");
result = ERR_INVALID_VALUE;
break;
}
if (id_tmp > INT_MAX || id_tmp < INT_MIN) {
resultReceiver_.append(HELP_MSG_INVALID_ID_ARGUMENT + "\n");
result = ERR_INVALID_VALUE;
break;
}
id = static_cast<int>(id_tmp);
break;
}
case 'd': {
if (optarg == nullptr) {
result = ERR_INVALID_VALUE;
resultReceiver_.append("fail: invalid display name.\n");
break;
}
if (optarg[0] == '-') {
result = ERR_INVALID_VALUE;
resultReceiver_.append("fail: invalid display name.\n");
break;
}
char *endptr;
displayId = std::strtoull(optarg, &endptr, DECIMAL_BASE);
if (*endptr != '\0') {
result = ERR_INVALID_VALUE;
resultReceiver_.append("fail: invalid display name.\n");
}
break;
}
default: {
break;
}
}
ACCOUNT_LOGD("end, result = %{public}d", result);
return result;
}
ErrCode AccountCommand::AnalyzeTypeArgument(OsAccountType &type)
{
ErrCode result = ERR_OK;
std::string typeByUser = optarg;
if (typeByUser == "admin") {
type = OsAccountType::ADMIN;
} else if (typeByUser == "normal") {
type = OsAccountType::NORMAL;
} else if (typeByUser == "guest") {
type = OsAccountType::GUEST;
} else if (typeByUser == "private") {
type = OsAccountType::PRIVATE;
} else if (typeByUser == "maintenance") {
type = OsAccountType::MAINTENANCE;
} else {
resultReceiver_.append(HELP_MSG_INVALID_TYPE_ARGUMENT + "\n");
result = ERR_INVALID_VALUE;
}
return result;
}
static bool IsExistFile(const std::string &path)
{
if (path.empty()) {
return false;
}
struct stat buf = {};
if (stat(path.c_str(), &buf) != 0) {
return false;
}
return S_ISREG(buf.st_mode);
}
static ErrCode GetListByPath(const std::string &path, std::vector<std::string> &list)
{
if (!IsExistFile(path)) {
ACCOUNT_LOGE("cannot find file, path = %{public}s", path.c_str());
return ERR_ACCOUNT_COMMON_FILE_OPEN_FAILED;
}
std::ifstream readFile;
readFile.open(path.c_str(), std::ios::in);
if (!readFile.is_open()) {
ACCOUNT_LOGE("cannot open file, path = %{public}s", path.c_str());
return ERR_ACCOUNT_COMMON_FILE_OPEN_FAILED;
}
std::string str;
while (getline(readFile, str)) {
ACCOUNT_LOGI("read file, str = %{public}s", str.c_str());
list.emplace_back(str);
}
readFile.close();
return ERR_OK;
}
ErrCode AccountCommand::AnalyzeListArgument(std::vector<std::string> &list)
{
std::string listPath = optarg;
return GetListByPath(listPath, list);
}
ErrCode AccountCommand::AnalyzeLocalIdArgument(int &id)
{
std::string idByUser = optarg;
if (!StrToInt(idByUser, id)) {
resultReceiver_.append(HELP_MSG_INVALID_ID_ARGUMENT + "\n");
return ERR_INVALID_VALUE;
}
return ERR_OK;
}
ErrCode AccountCommand::AnalyzeConstraintArgument(std::vector<std::string> &constraints)
{
std::string constraintsByUser = optarg;
ACCOUNT_LOGD("constraintsByUser = %{public}s", constraintsByUser.c_str());
constraints.clear();
std::string constraint = "";
std::string delimiter = ",";
size_t last = 0;
size_t next = 0;
while ((next = constraintsByUser.find(delimiter, last)) != std::string::npos) {
constraint = constraintsByUser.substr(last, next - last);
ACCOUNT_LOGD("constraint = %{public}s", constraint.c_str());
constraints.emplace_back(constraint);
last = next + 1;
}
constraint = constraintsByUser.substr(last);
ACCOUNT_LOGD("constraint = %{public}s", constraint.c_str());
constraints.emplace_back(constraint);
return ERR_OK;
}
}
}