* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
*
* 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 <fcntl.h>
#include <cerrno>
#include <cstdlib>
#include <climits>
#include <unistd.h>
#include <string>
#include <set>
#include <map>
#include <functional>
#include "actor/buslog.hpp"
#include "exec/reap_process.hpp"
#include "securec.h"
#include "utils/os_utils.hpp"
#include "exec/exec.hpp"
#include <pty.h>
#include <termios.h>
#include <sys/ioctl.h>
namespace litebus {
static const int MAX_PARAMS_SIZE = 2048;
namespace execinternal {
void CloseFD(const std::initializer_list<int> &fds)
{
for (auto p = fds.begin(); p != fds.end(); p++) {
if (*p >= 0) {
(void)::close(*p);
}
}
}
void CloseAllIO(const InFileDescriptor &stdIn, const OutFileDescriptor &stdOut, const OutFileDescriptor &stdError)
{
std::initializer_list<int> fds = {
stdIn.read, stdIn.write.IsNone() ? -1 : stdIn.write.Get(), stdOut.read.IsNone() ? -1 : stdOut.read.Get(),
stdOut.write, stdError.read.IsNone() ? -1 : stdError.read.Get(), stdError.write
};
execinternal::CloseFD(fds);
}
static void DoClean(const Future<Option<int>> &result, const std::shared_ptr<Promise<Option<int>>> &promise,
const std::shared_ptr<Exec>)
{
if (result.IsInit()) {
BUSLOG_INFO("Promise is initing");
}
if (!result.IsError()) {
promise->SetValue(result.Get());
} else {
promise->SetFailed(result.GetErrorCode());
}
BUSLOG_INFO("Doclean after check");
}
pid_t CloneProcess(const std::function<int()> &func)
{
pid_t pid = (::fork());
if (pid == -1) {
return -1;
} else if (pid == 0) {
::exit(func());
} else {
BUSLOG_DEBUG("Clone child succ pid:{}", pid);
return pid;
}
}
int ExecMainFunc(const std::string &path, char **argVal, char **environmentParam, const InFileDescriptor &stdIn,
const OutFileDescriptor &stdOut, const OutFileDescriptor &stdError,
const std::vector<std::function<void()>> &childInitHooks)
{
HandleIOAndHook(stdIn, stdOut, stdError, childInitHooks);
char **backupEnv = environ;
environ = environmentParam;
int r = (::execvp(path.c_str(), argVal));
environ = backupEnv;
if (r < 0) {
::_exit(errno);
}
return r;
}
void HandleIOAndHook(const InFileDescriptor &stdIn, const OutFileDescriptor &stdOut, const OutFileDescriptor &stdError,
const std::vector<std::function<void()>> &childInitHooks)
{
if (stdIn.write.IsSome()) {
(void)::close(stdIn.write.Get());
}
if (stdOut.read.IsSome()) {
(void)::close(stdOut.read.Get());
}
if (stdError.read.IsSome()) {
(void)::close(stdError.read.Get());
}
bool failed = false;
do {
failed = (::dup2(stdIn.read, STDIN_FILENO) == -1 && errno == EINTR);
} while (failed);
do {
failed = (::dup2(stdOut.write, STDOUT_FILENO) == -1 && errno == EINTR);
} while (failed);
do {
failed = (::dup2(stdError.write, STDERR_FILENO) == -1 && errno == EINTR);
} while (failed);
if (stdIn.read != STDIN_FILENO && stdIn.read != STDOUT_FILENO && stdIn.read != STDERR_FILENO) {
(void)::close(stdIn.read);
}
if (stdOut.write != STDIN_FILENO && stdOut.write != STDOUT_FILENO && stdOut.write != STDERR_FILENO
&& stdOut.write != stdIn.read) {
(void)::close(stdOut.write);
}
if (stdError.write != STDIN_FILENO && stdError.write != STDOUT_FILENO && stdError.write != STDERR_FILENO
&& stdError.write != stdIn.read && stdError.write != stdOut.write) {
(void)::close(stdError.write);
}
auto it = childInitHooks.begin();
for (; it != childInitHooks.end(); ++it) {
(*it)();
}
}
char **GenEnvFormMap(const Option<std::map<std::string, std::string>> &environment, unsigned int &envSize)
{
char **environmentParam = environ;
if (environment.IsSome()) {
envSize = environment.Get().size();
if (environment.Get().size() > MAX_PARAMS_SIZE) {
BUSLOG_WARN("Environment size overflow size:{}", environment.Get().size());
envSize = MAX_PARAMS_SIZE;
}
environmentParam = new (std::nothrow) char *[envSize + 1];
BUS_OOM_EXIT(environmentParam);
size_t index = 0;
for (auto it = environment.Get().begin(); index < envSize; ++it, index++) {
std::string entry = it->first + "=" + it->second;
environmentParam[index] = new (std::nothrow) char[entry.size() + 1];
BUS_OOM_EXIT(environmentParam[index]);
(void)memset_s(environmentParam[index], entry.size() + 1, 0, entry.size() + 1);
errno_t e = strncpy_s(environmentParam[index], entry.size() + 1, entry.c_str(), entry.size());
if (e != 0) {
BUSLOG_ERROR("strncpy_s env Error, entry:{},Error:{}", entry, e);
BUS_EXIT("GenEnvFormMap strncpy_s env Error");
}
}
environmentParam[index] = nullptr;
}
return environmentParam;
}
Try<pid_t> CloneExec(const std::string &path, std::vector<std::string> argv,
const Option<std::map<std::string, std::string>> &environment, const InFileDescriptor &stdIn,
const OutFileDescriptor &stdOut, const OutFileDescriptor &stdError,
const std::vector<std::function<void()>> &childInitHooks,
const std::vector<std::function<void(pid_t)>> &)
{
char **argValues = new (std::nothrow) char *[argv.size() + 1];
BUS_OOM_EXIT(argValues);
for (size_t i = 0; i < argv.size(); i++) {
argValues[i] = const_cast<char *>(argv[i].c_str());
}
argValues[argv.size()] = nullptr;
unsigned int envSize = 0;
char **environmentParam = GenEnvFormMap(environment, envSize);
pid_t pid = CloneProcess(
std::bind(&ExecMainFunc, path, argValues, environmentParam, stdIn, stdOut, stdError, childInitHooks));
delete[] argValues;
if (environment.IsSome()) {
for (unsigned int i = 0; i < envSize; i++) {
delete[] environmentParam[i];
}
delete[] environmentParam;
}
BUSLOG_DEBUG("Finish clone a exec command pid:{}", pid);
execinternal::CloseFD({ stdIn.read, stdOut.write, stdError.write });
return pid;
}
int CloseOnExec(const InFileDescriptor &stdIn, const OutFileDescriptor &stdOut, const OutFileDescriptor &stdError)
{
std::set<int> fdSet;
(void)fdSet.insert(stdIn.read);
(void)fdSet.insert(stdIn.write.IsNone() ? -1 : stdIn.write.Get());
(void)fdSet.insert(stdOut.read.IsNone() ? -1 : stdOut.read.Get());
(void)fdSet.insert(stdOut.write);
(void)fdSet.insert(stdError.read.IsNone() ? -1 : stdError.read.Get());
(void)fdSet.insert(stdError.write);
auto it = fdSet.begin();
for (; it != fdSet.end(); ++it) {
if (*it >= 0) {
int r = os::CloseOnExec(*it);
if (r == -1) {
return r;
}
}
}
return 0;
}
}
ExecIO ExecIO::CreatePipeIO()
{
const int PIPE_PAIR_SIZE = 2;
std::function<Try<InFileDescriptor>()> inFunc = []() {
int pipeFd[PIPE_PAIR_SIZE];
if (::pipe(pipeFd) == -1) {
BUSLOG_ERROR("Create Pipe IO in failed");
return Try<InFileDescriptor>(Failure(IO_CREATE_ERROR));
}
InFileDescriptor fileDescriptors;
fileDescriptors.read = pipeFd[0];
fileDescriptors.write = pipeFd[1];
return Try<InFileDescriptor>(fileDescriptors);
};
std::function<Try<OutFileDescriptor>()> outFunc = []() {
int pipeFd[PIPE_PAIR_SIZE];
if (::pipe(pipeFd) == -1) {
BUSLOG_ERROR("Create Pipe IO in failed");
return Try<OutFileDescriptor>(Failure(IO_CREATE_ERROR));
}
OutFileDescriptor fileDescriptors;
fileDescriptors.read = pipeFd[0];
fileDescriptors.write = pipeFd[1];
return Try<OutFileDescriptor>(fileDescriptors);
};
return ExecIO(inFunc, outFunc);
}
ExecIO ExecIO::CreateFileIO(const std::string &filePath)
{
std::function<Try<InFileDescriptor>()> inFunc = [filePath]() {
char path[PATH_MAX + 1] = { 0x00 };
if (strlen(filePath.c_str()) > PATH_MAX || nullptr == realpath(filePath.c_str(), path)) {
return Try<InFileDescriptor>(Failure(IO_CREATE_ERROR));
}
int fd = (::open(path, O_RDONLY | O_CLOEXEC, S_IRUSR | S_IWUSR | S_IRGRP));
if (fd < 0) {
BUSLOG_ERROR("Create File IO in failed:{}", fd);
return Try<InFileDescriptor>(Failure(IO_CREATE_ERROR));
}
InFileDescriptor fileDescriptors;
fileDescriptors.read = fd;
return Try<InFileDescriptor>(fileDescriptors);
};
std::function<Try<OutFileDescriptor>()> outFunc = [filePath]() {
char path[PATH_MAX + 1] = { 0x00 };
if (strlen(filePath.c_str()) > PATH_MAX || nullptr == realpath(filePath.c_str(), path)) {
return Try<OutFileDescriptor>(Failure(IO_CREATE_ERROR));
}
int fd = ::open(path, O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC, S_IRUSR | S_IWUSR | S_IRGRP);
if (fd < 0) {
BUSLOG_ERROR("Create File IO in failed:{}", fd);
return Try<OutFileDescriptor>(Failure(IO_CREATE_ERROR));
}
OutFileDescriptor fileDescriptors;
fileDescriptors.write = fd;
return Try<OutFileDescriptor>(fileDescriptors);
};
return ExecIO(inFunc, outFunc);
}
ExecIO ExecIO::CreateFDIO(int fd)
{
std::function<Try<InFileDescriptor>()> inFunc = [fd]() {
int dup = (::dup(fd));
if (dup < 0) {
BUSLOG_ERROR("Create FD IO in failed");
return Try<InFileDescriptor>(Failure(IO_CREATE_ERROR));
}
InFileDescriptor fileDescriptors;
fileDescriptors.read = dup;
return Try<InFileDescriptor>(fileDescriptors);
};
std::function<Try<OutFileDescriptor>()> outFunc = [fd]() {
int dup = (::dup(fd));
if (dup < 0) {
BUSLOG_ERROR("Create FD IO in failed");
return Try<OutFileDescriptor>(Failure(IO_CREATE_ERROR));
}
OutFileDescriptor fileDescriptors;
fileDescriptors.write = dup;
return Try<OutFileDescriptor>(fileDescriptors);
};
return ExecIO(inFunc, outFunc);
}
namespace Shell {
constexpr const char *CMD = "sh";
constexpr const char *ARG0 = "sh";
constexpr const char *ARG1 = "-c";
}
std::shared_ptr<Exec> Exec::CreateExec(const std::string &command,
const Option<std::map<std::string, std::string>> &environment,
const ExecIO &stdIn, const ExecIO &stdOut, const ExecIO &stdError,
const std::vector<std::function<void()>> &childInitHooks,
const std::vector<std::function<void(pid_t)>> &parentInitHooks,
const bool &enableReap)
{
std::vector<std::string> argv = { Shell::ARG0, Shell::ARG1, command };
return CreateExec(Shell::CMD, argv, environment, stdIn, stdOut, stdError, childInitHooks, parentInitHooks,
enableReap);
}
std::shared_ptr<Exec> Exec::CreateExec(const std::string &path, const std::vector<std::string> &argv,
const Option<std::map<std::string, std::string>> &environment,
const ExecIO &stdIn, const ExecIO &stdOut, const ExecIO &stdError,
const std::vector<std::function<void()>> &childInitHooks,
const std::vector<std::function<void(pid_t)>> &parentInitHooks,
const bool &enableReap)
{
InFileDescriptor tStdIn;
OutFileDescriptor tStdOut;
OutFileDescriptor tStdError;
Try<InFileDescriptor> input = stdIn.inputSetup();
if (input.IsError()) {
BUSLOG_ERROR("input setup failed!");
return nullptr;
}
tStdIn = input.Get();
Try<OutFileDescriptor> output = stdOut.outputSetup();
if (output.IsError()) {
BUSLOG_ERROR("output setup failed!");
litebus::execinternal::CloseAllIO(tStdIn, tStdOut, tStdError);
return nullptr;
}
tStdOut = output.Get();
output = stdError.outputSetup();
if (output.IsError()) {
BUSLOG_ERROR("output setup failed!");
litebus::execinternal::CloseAllIO(tStdIn, tStdOut, tStdError);
return nullptr;
}
tStdError = output.Get();
int r = execinternal::CloseOnExec(tStdIn, tStdOut, tStdError);
if (r == -1) {
BUSLOG_ERROR("CloseOnExec setup failed!");
execinternal::CloseAllIO(tStdIn, tStdOut, tStdError);
return nullptr;
}
std::shared_ptr<Exec> tExec = std::make_shared<Exec>();
Try<pid_t> pid =
execinternal::CloneExec(path, argv, environment, tStdIn, tStdOut, tStdError, childInitHooks, parentInitHooks);
if (pid.IsError()) {
BUSLOG_ERROR("Clone a exec command failed!");
litebus::execinternal::CloseAllIO(tStdIn, tStdOut, tStdError);
return nullptr;
}
tExec->pid = pid.Get();
tExec->inStream = tStdIn.write;
tExec->outStream = tStdOut.read;
tExec->errorStream = tStdError.read;
std::shared_ptr<Promise<Option<int>>> promise = std::make_shared<Promise<Option<int>>>();
tExec->future = promise->GetFuture();
if (enableReap) {
(void)litebus::ReapInActor(tExec->pid)
.OnComplete(std::bind(execinternal::DoClean, std::placeholders::_1, promise, tExec));
} else {
promise->SetValue(litebus::Option<int>(0));
}
return tExec;
}
Try<PtyExecIO> PtyExecIO::Create(int rows, int cols)
{
int master;
int slave;
struct winsize ws;
ws.ws_row = static_cast<unsigned short>(rows);
ws.ws_col = static_cast<unsigned short>(cols);
ws.ws_xpixel = 0;
ws.ws_ypixel = 0;
if (openpty(&master, &slave, nullptr, nullptr, &ws) == -1) {
BUSLOG_ERROR("Failed to create PTY pair, errno: {}", errno);
return Try<PtyExecIO>(Failure(IO_CREATE_ERROR));
}
BUSLOG_DEBUG("PTY created, master: {}, slave: {}", master, slave);
auto ptyStdIn = std::make_shared<ExecIO>(
[slave]() {
InFileDescriptor fd;
fd.read = slave;
return Try<InFileDescriptor>(fd);
},
[master]() {
OutFileDescriptor fd;
fd.write = master;
return Try<OutFileDescriptor>(fd);
}
);
auto ptyStdOut = std::make_shared<ExecIO>(
[slave]() {
InFileDescriptor fd;
fd.read = slave;
return Try<InFileDescriptor>(fd);
},
[slave, master]() {
OutFileDescriptor fd;
fd.read = master;
fd.write = slave;
return Try<OutFileDescriptor>(fd);
}
);
PtyExecIO pty;
pty.masterFd = master;
pty.slaveFd = slave;
pty.stdIn = std::move(ptyStdIn);
pty.stdErr = ptyStdOut;
pty.stdOut = std::move(ptyStdOut);
return pty;
}
int PtyExecIO::Resize(int rows, int cols)
{
if (masterFd < 0) {
return -1;
}
struct winsize ws;
ws.ws_row = static_cast<unsigned short>(rows);
ws.ws_col = static_cast<unsigned short>(cols);
ws.ws_xpixel = 0;
ws.ws_ypixel = 0;
int ret = ioctl(masterFd, TIOCSWINSZ, &ws);
if (ret == 0) {
BUSLOG_DEBUG("PTY resized to {}x{}", rows, cols);
} else {
BUSLOG_ERROR("Failed to resize PTY, errno: {}", errno);
}
return ret;
}
void PtyExecIO::Close()
{
if (masterFd >= 0) {
close(masterFd);
masterFd = -1;
}
if (slaveFd >= 0) {
close(slaveFd);
slaveFd = -1;
}
stdIn.reset();
stdOut.reset();
stdErr.reset();
}
}