Application Execution of Shell Commands and Its Basic Principles
If an application needs to execute system Shell commands, it can do so through the runCmd(command: string, options?: ConditionType): ChildProcess function of @ohos.process. Please note that this metho
If an application needs to execute system Shell commands, it can do so through the runCmd(command: string, options?: ConditionType): ChildProcess
function of @ohos.process
.
Please note that this method has some limitations. Generally, it is not recommended to use, and this article is only for reference.
How to use
Download Full SDK
Since this API is a system application privilege and is not available to ordinary applications, it is necessary to download the Full SDK. If you already have the Full SDK, this step can be skipped. The Full SDK of version 11 or 12 can be downloaded from the daily builds of OpenHarmony CI. The link is: https://ci.openharmony.cn/workbench/cicd/dailybuild/dailylist
Download the API 11 SDK by selecting the OpenHarmony-4.1-Release branch, and download the API 12 SDK by selecting the OpenHarmony-5.0.2-Release branch.
After downloading and decompressing, point the OpenHarmony SDK to this directory through the settings of DevEco Studio.
Disable SeLinux
Due to the restrictions of SeLinux, when ordinary applications access the executable files under /bin
, a permission error will be reported. Therefore, SeLinux needs to be disabled. It can be done by the following commands:
hdc shell setenforce 0
System Application Privilege
Since this API has the flag of “@systemapi Hide this for inner system use”, the application needs to be a system application. You can either pre-install the application into the system or obtain system application permissions through the APL method. The methods will not be elaborated here. If necessary, you can search it on the Internet.
Codes
import process from '@ohos.process';
import buffer from '@ohos.buffer';
async function execCmd(cmd: string): Promise<string> {
let childProcess: process.ChildProcess = process.runCmd(cmd);
let outputString: string = 'Failed';
let errorOutputString: string = '';
try {
// waiting for child process finish
const exitCode = await childProcess.wait();
// get the standard output of child process
const output = await childProcess.getOutput();
outputString = buffer.from(output).toString('UTF-8');
// get the standard error output of child process
if (outputString === '\u0000') {
const errorOutput = await childProcess.getErrorOutput();
errorOutputString = buffer.from(errorOutput).toString('UTF-8');
}
} catch (error) {
// handle errors
Logger.error(TAG, JSON.stringify(error) ?? '');
throw error as Error;
}
if (errorOutputString) {
throw new Error(errorOutputString);
}
return outputString;
}
Above code defines the execCmd
function, and the main process is as follows:
- After calling
runCmd
, achildProcess
object will be returned, which represents child process. - Call
childProcess.wait()
to wait for child process to finish execution. - After child process finishes execution, use
childProcess.getOutput()
to obtain the result of the command execution, and usechildProcess.getErrorOutput()
to obtain the error after the command execution. - If there is an error, throw an exception.
- Since the Shell command will be executed in the child process, there is no need to worry about blocking the main thread of the main process.
execCmd
function is easy to use. For example, to execute a simple ls
command:
execCmd('ls')
.then(result => {})
.catch(e => {});
The input parameter can be replaced with the command that needs to be executed.
Differences between 4.1 and 5.0
On 4.1 system, for the runCmd
function, you only need to pass in the name of the executable file, such as ls
or reboot
. However, on 5.0, you need to pass in the full path of the executable file, like runCmd('/bin/ls')
.
Precautions
After the
runCmd
function is executed, system will fork a child process from the current application process, which is represented by thechildProcess
object. The Shell command will be executed in the child process. This means that the permissions of the child process are the same as those of the application, and it doesn't have root privileges. Usually, application itself can't access files outside the sandbox directory, child process can't either.After testing,
runCmd
can only support a part of executable programs in the/bin
directory.If the binary program accesses directories that the application can't access, unpredictable errors will occur. Similarly, errors will occur if the application calls APIs or operations that it doesn't have permission to access.
If you have a self-written binary program that needs to be executed by the application, you can consider placing the program in the application's assets directory. Then during runtime, copy the file to the application's sandbox directory, and then execute the program by using the combination of the sandbox directory and the file name. You can refer to: https://gitee.com/kunyuan-hongke/openharmonyevaluate/blob/master/entry/src/main/ets/test/CpuMapTest.ets
Basic Principles
The code implementation of runCmd
is located in commonlibrary\ets_utils\js_sys_module\process\native_module_process.cpp
:
static napi_value ChildProcessConstructor(napi_env env, napi_callback_info info)
{
napi_value thisVar = nullptr;
void* data = nullptr;
size_t argc = 2; // 2:The number of parameters is 2
napi_value args[2] = { nullptr }; // 2:The number of parameters is 2
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &thisVar, &data));
DealType(env, args, argc);
auto objectInfo = new ChildProcess();
objectInfo->InitOptionsInfo(env, args[1]);
objectInfo->Spawn(env, args[0]);
NAPI_CALL(env, napi_wrap(
env, thisVar, objectInfo,
[](napi_env env, void* data, void* hint) {
auto objectResult = reinterpret_cast<ChildProcess*>(data);
if (objectResult != nullptr) {
delete objectResult;
objectResult = nullptr;
}
},
nullptr, nullptr));
return thisVar;
}
In fact, it instantiates a ChildProcess
object and calls its Spawn
function:
void ChildProcess::Spawn(napi_env env, napi_value command)
{
int ret = pipe(stdOutFd_);
if (ret < 0) {
HILOG_ERROR("ChildProcess:: pipe1 failed %{public}d", errno);
return;
}
ret = pipe(stdErrFd_);
if (ret < 0) {
HILOG_ERROR("ChildProcess:: pipe2 failed %{public}d", errno);
return;
}
std::string strCommnd = RequireStrValue(env, command);
pid_t pid = fork();
if (!pid) {
close(stdErrFd_[0]);
close(stdOutFd_[0]);
dup2(stdOutFd_[1], 1);
dup2(stdErrFd_[1], 2); // 2:The value of parameter
if (execl("/bin/sh", "sh", "-c", strCommnd.c_str(), nullptr) == -1) {
HILOG_ERROR("ChildProcess:: execl command failed");
_exit(127); // 127:The parameter value
}
} else if (pid > 0) {
if (optionsInfo_ == nullptr) {
HILOG_ERROR("ChildProcess:: optionsInfo_ is nullptr");
return;
}
optionsInfo_->pid = pid;
ppid_ = getpid();
CreateWorker(env);
napi_value resourceName = nullptr;
napi_create_string_utf8(env, "TimeoutListener", strlen("TimeoutListener"), &resourceName);
napi_create_async_work(
env, nullptr, resourceName, TimeoutListener,
[](napi_env env, napi_status status, void* data) {
OptionsInfo* optionsInfo = reinterpret_cast<OptionsInfo*>(data);
napi_delete_async_work(env, optionsInfo->worker);
delete optionsInfo;
optionsInfo = nullptr;
},
reinterpret_cast<void*>(optionsInfo_), &optionsInfo_->worker);
napi_queue_async_work_with_qos(env, optionsInfo_->worker, napi_qos_user_initiated);
close(stdErrFd_[1]);
close(stdOutFd_[1]);
} else {
HILOG_ERROR("ChildProcess:: child process create failed");
}
}
In the Spawn
function, it follows the classic writing of the fork
function:
- First, create two pipes through the
pipe
function. The child process can write results through this pipe, and the parent process reads the results through the pipe. - Obtain the command that need to be executed.
- Create a child process through
fork
. The created child process is basically same to the parent process. Two processes all start to execute the code belowfork
function, and the difference ispid
variable .- If
pid
is equal to 0, it means this code is executed in the child process. - If
pid
is greater than 0, it means this code is executed in the parent process. - If
pid
is less than 0, it means this code is executed in the parent process, and an error occurred while creating the child process.
- If
- In the child process, first use the
dup2
function to replace the two previously created pipes with standard output and error output, to pass the results to the parent process. Then, use theexecl
function to reload the child process to execute an executable file. That is, execute/bin/sh -c command
, thus achieving the purpose of executing the command. - In the main process, the main procedures are to create tasks to receive the output of the child process and callbacks related to timeouts.
更多推荐
所有评论(0)