* Copyright (c) 2026 Huawei Technologies Co., Ltd.
* This program is free software, you can redistribute it and/or modify it under the terms and conditions of
* CANN Open Software License Agreement Version 2.0 (the "License").
* Please refer to the License for details. You may not use this file except in compliance with the License.
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
* See LICENSE in the root of the software repository for the full text of the License.
*/
#include "sim_plugin.h"
#include <cstdint>
#include <cstdlib>
#include <fcntl.h>
#include <fstream>
#include <iostream>
#include <json.hpp>
#include <sys/prctl.h>
#include <sys/wait.h>
#include <thread>
#include <unistd.h>
#include "sim_common_defs.h"
#include "sim_common_macro.h"
#include "sim_log.h"
using namespace HcclSim;
const int HcclPlugin::MAX_SCAN_DEPTH = 2;
const std::string HcclPlugin::PLUGIN_PATH = "/plugin";
const std::string HcclPlugin::MANIFEST_FILE = "/manifest.json";
const std::string HcclPlugin::Manifest::pluginName = "name";
const std::string HcclPlugin::Manifest::pluginVersion = "version";
const std::string HcclPlugin::Manifest::pluginEntry = "entry";
const std::string HcclPlugin::Manifest::pluginDependency::hostVersion = "min_core_version";
const std::string HcclPlugin::PluginMessage::messageType = "type";
const std::string HcclPlugin::PluginMessage::messageAction = "action";
const std::string HcclPlugin::PluginMessage::messagePayload = "payload";
HcclPlugin::HcclPlugin(const std::string& pluginPath)
{
m_pluginPath = pluginPath;
std::string manifestPath = pluginPath + HcclPlugin::MANIFEST_FILE;
std::ifstream file(manifestPath);
if (!file.is_open()) {
throw std::runtime_error("Manifest not found at: " + manifestPath);
}
try {
file >> m_manifest;
} catch (const nlohmann::json::parse_error& e) {
throw std::runtime_error("JSON parse error: " + std::string(e.what()));
}
HcclVmResult ret = Start();
if (ret != HcclVmResult::HCCL_SIM_SUCCESS) {
throw std::runtime_error("Failed to launch plugin: " + GetTag());
}
}
HcclPlugin::~HcclPlugin() { Stop(); }
HcclVmResult HcclPlugin::Start()
{
std::string entryCmd = m_manifest.value(HcclPlugin::Manifest::pluginEntry, "");
if (entryCmd.empty()) {
HCCL_VM_INFO("Empty Entry Command");
return HcclVmResult::HCCL_SIM_E_PARA;
}
HCCL_VM_INFO("Starting plugin [{}]", GetTag());
int32_t fds[2];
if (pipe(fds) == -1) {
return HcclVmResult::HCCL_SIM_E_INTERNAL;
}
m_pid = fork();
if (m_pid < 0) {
::close(fds[0]);
::close(fds[1]);
return HcclVmResult::HCCL_SIM_E_INTERNAL;
}
if (m_pid == 0) {
prctl(PR_SET_PDEATHSIG, SIGTERM);
::close(fds[1]);
if (dup2(fds[0], STDIN_FILENO) == -1) {
_exit(EXIT_FAILURE);
}
::close(fds[0]);
if (chdir(m_pluginPath.c_str()) != 0) {
_exit(EXIT_FAILURE);
}
auto argv = PrepareArgs(entryCmd);
execvp(argv[0], argv.data());
perror("execvp");
std::_Exit(EXIT_FAILURE);
} else {
::close(fds[0]);
m_stdinFd = fds[1];
int flags = fcntl(m_stdinFd, F_GETFL, 0);
fcntl(m_stdinFd, F_SETFL, flags | O_NONBLOCK);
std::this_thread::sleep_for(std::chrono::milliseconds(5));
int status = 0;
pid_t res = waitpid(m_pid, &status, WNOHANG);
if (res == 0) {
HCCL_VM_INFO("Plugin [{}] started", GetTag());
return HcclVmResult::HCCL_SIM_SUCCESS;
} else if (res == m_pid) {
if (WIFEXITED(status)) {
int exitCode = WEXITSTATUS(status);
HCCL_VM_ERROR("Plugin [{}] failed to start with code [{}].", GetTag(), exitCode);
}
::close(m_stdinFd);
m_stdinFd = -1;
m_pid = -1;
return HcclVmResult::HCCL_SIM_E_INTERNAL;
} else {
::close(m_stdinFd);
m_stdinFd = -1;
m_pid = -1;
return HcclVmResult::HCCL_SIM_E_INTERNAL;
}
return HcclVmResult::HCCL_SIM_SUCCESS;
}
}
HcclVmResult HcclPlugin::Stop()
{
if (m_pid <= 0) {
return HcclVmResult::HCCL_SIM_SUCCESS;
}
int status;
pid_t result = waitpid(m_pid, &status, WNOHANG);
if (result == m_pid || (result == -1 && errno == ECHILD)) {
m_pid = -1;
std::lock_guard<std::mutex> lock(m_mutex);
if (m_stdinFd != -1) {
::close(m_stdinFd);
m_stdinFd = -1;
}
return HcclVmResult::HCCL_SIM_SUCCESS;
}
SendMessage(PLUGIN_MESSAGE_TYPE::COMMAND, "stop");
{
std::lock_guard<std::mutex> lock(m_mutex);
if (m_stdinFd != -1) {
::close(m_stdinFd);
m_stdinFd = -1;
}
}
bool exited = false;
auto start = std::chrono::steady_clock::now();
while (true) {
int32_t stas;
pid_t ret = waitpid(m_pid, &stas, WNOHANG);
if (ret == m_pid || (ret == -1 && errno == ECHILD)) {
exited = true;
break;
}
auto now = std::chrono::steady_clock::now();
if (std::chrono::duration_cast<std::chrono::seconds>(now - start).count() >= 5) {
break;
}
std::this_thread::sleep_for(std::chrono::seconds(1));
}
if (!exited) {
HCCL_VM_ERROR("Plugin [{}] detected as not exiting normally.", GetTag());
HCCL_VM_ERROR("[ACTION REQUIRED] Please manually check or terminate PID: {:d}", m_pid);
} else {
HCCL_VM_INFO("Plugin [{}] exited successfully.", GetTag());
}
m_pid = -1;
return HcclVmResult::HCCL_SIM_SUCCESS;
}
HcclVmResult HcclPlugin::SendMessage(PLUGIN_MESSAGE_TYPE type, const std::string& action, const nlohmann::json& payload)
{
if (m_pid <= 0 || m_stdinFd == -1) {
return HcclVmResult::HCCL_SIM_SUCCESS;
}
nlohmann::json msg;
msg[HcclPlugin::PluginMessage::messageType] = static_cast<int32_t>(type);
msg[HcclPlugin::PluginMessage::messageAction] = action;
msg[HcclPlugin::PluginMessage::messagePayload] = payload;
std::string packet = msg.dump(-1) + "\n";
std::lock_guard<std::mutex> lock(m_mutex);
ssize_t written = ::write(m_stdinFd, packet.c_str(), packet.size());
if (written == static_cast<ssize_t>(packet.size())) {
return HcclVmResult::HCCL_SIM_SUCCESS;
} else {
return HcclVmResult::HCCL_SIM_E_INTERNAL;
}
}
bool HcclPlugin::IsRunning() const
{
if (m_pid <= 0) {
return false;
}
int32_t status;
pid_t result = waitpid(m_pid, &status, WNOHANG);
if (result == 0) {
return true;
} else if (result == m_pid) {
return false;
} else {
return false;
}
}
std::string HcclPlugin::GetTag() const { return m_manifest.value(HcclPlugin::Manifest::pluginName, "Unknown"); }
std::vector<char*> HcclPlugin::PrepareArgs(const std::string& command)
{
std::vector<char*> argv;
std::vector<char> buffer(command.begin(), command.end());
buffer.push_back('\0');
char* saveptr = nullptr;
char* token = strtok_r(buffer.data(), " ", &saveptr);
while (token != nullptr) {
argv.push_back(strdup(token));
token = strtok_r(nullptr, " ", &saveptr);
}
argv.push_back(nullptr);
return argv;
}