* This file is part of the MindStudio project.
* Copyright (c) 2025 Huawei Technologies Co.,Ltd.
*
* MindStudio is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* 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 FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
* ------------------------------------------------------------------------- */
#include "FileSystem.h"
#include <map>
#include <sys/stat.h>
#include <iterator>
#include "utils/InjectLogger.h"
#include "utils/Path.h"
#include "Ustring.h"
using namespace std;
namespace {
std::map<std::string, std::pair<uint32_t, bool>> GetFilePermission()
{
std::map<std::string, std::pair<uint32_t, bool>> filePermission = {
{"json", {S_IRUSR, true}}, {"cpp", {S_IRUSR, false}},
{"bin", {S_IRUSR, true}}, {"kernel", {S_IRUSR, true}},
{"dump", {S_IRUSR, true}}, {"dir", {S_IRUSR, true}},
{"so", {S_IRUSR, false}}, {"exe", {S_IEXEC | S_IRUSR, true}}
};
return filePermission;
}
inline bool IsReadable(const std::string &checkPath)
{
struct stat fileStat{};
return (stat(checkPath.c_str(), &fileStat) == 0) && (fileStat.st_mode & S_IRUSR) != 0;
}
bool CheckPathPermission(const std::string &path, unsigned int fileMode)
{
if ((fileMode & S_IRUSR) != 0 && !IsReadable(path)) {
ERROR_LOG("path: %s is not readable", path.c_str());
return false;
}
if ((fileMode & S_IWUSR) != 0 && !IsWritable(path)) {
ERROR_LOG("path: %s is not writable", path.c_str());
return false;
}
if ((fileMode & S_IXUSR) != 0 && !IsExecutable(path)) {
ERROR_LOG("path: %s is not executable", path.c_str());
return false;
}
return true;
}
}
inline bool IsRootUser()
{
constexpr __uid_t root = 0;
return getuid() == root;
}
size_t ReadBinary(std::string const &filename, vector<char> &data)
{
size_t fileSize = GetFileSize(filename);
data.resize(fileSize);
uint8_t *buffer = reinterpret_cast<uint8_t *>(data.data());
if (!ReadFile(filename, buffer, fileSize)) {
return 0;
}
return fileSize;
}
bool MkdirRecusively(std::string const &path, mode_t mode)
{
std::vector<std::string> dirs;
if (path.empty()) {
return false;
}
SplitString(Path(path).PathCanonicalize().ToString(), *PATH_SEP, dirs);
if (dirs.empty()) {
ERROR_LOG("Mkdir [%s] failed because path is empty.", path.c_str());
return false;
}
if (IsDir(path)) {
return true;
}
std::string current;
for (auto it = dirs.cbegin(); it != dirs.cend(); ++it) {
if (it == dirs.cbegin()) {
current = *it;
} else {
current.append(PATH_SEP + *it);
}
if (*it == "") {
continue;
}
if (IsDir(current)) {
continue;
}
if (mkdir(current.c_str(), mode) < 0) {
std::string msg;
if (IsDir(current) && CheckOwnerPermission(current, msg)) {
WARN_LOG("Mkdir dir %s failed, dir already exist, msg : %s", current.c_str(), msg.c_str());
continue;
}
ERROR_LOG("Mkdir [%s] failed", path.c_str());
return false;
}
}
return true;
}
bool CopyFile(const std::string &srcPath, const std::string &destPath)
{
try {
std::experimental::filesystem::copy(srcPath, destPath);
}
catch (const std::experimental::filesystem::filesystem_error& e) {
ERROR_LOG("Failed to copy file,error reason is %s", e.what());
return false;
}
return true;
}
const std::string &GetEnv(const std::string &envKey)
{
static std::map<std::string, std::string> envMap;
auto envIter = envMap.find(envKey);
if (envIter != envMap.end()) {
return envIter->second;
}
char *value = secure_getenv(envKey.c_str());
std::string env = (value == nullptr) ? "" : std::string(value);
envMap[envKey] = env;
return envMap[envKey];
}
bool RollbackPath(std::string &path, uint32_t rollNum)
{
std::string tmpPath = path;
while (rollNum > 0) {
rollNum--;
std::size_t found = tmpPath.find_last_of('/');
if (found == std::string::npos) {
return false;
}
tmpPath = tmpPath.substr(0, found);
size_t len = tmpPath.size();
while (len <= tmpPath.size() && len > 0 && tmpPath[len - 1] == '/') {
len--;
}
if (len <= tmpPath.size() && len > 0) {
tmpPath = tmpPath.substr(0, len);
}
}
path = tmpPath.empty() ? "/" : tmpPath;
return true;
}
bool IsPathExists(const std::string &path)
{
struct stat buf{};
return stat(path.c_str(), &buf) == 0;
}
void RemoveAll(const std::string& filePath)
{
using namespace std::experimental::filesystem;
if (!IsPathExists(filePath)) {
return;
}
remove_all(filePath);
}
void CreateSymlink(const std::string &src, const std::string &dst)
{
using namespace std::experimental::filesystem;
std::string absSrc = Realpath(src);
if (!IsPathExists(absSrc)) {
DEBUG_LOG("File not exist,failed to create link");
return;
}
std::error_code error;
create_symlink(absSrc, dst, error);
if (error) {
DEBUG_LOG("Failed create link from %s to %s, error %s", absSrc.c_str(), dst.c_str(),
error.message().c_str());
}
}
size_t GetFileSize(const std::string &filePath)
{
if (!IsExist(filePath)) {
return 0;
}
struct stat fileStat{};
if (stat(filePath.c_str(), &fileStat) != 0 || !S_ISREG(fileStat.st_mode)) {
return 0;
}
auto filesize = static_cast<size_t>(fileStat.st_size);
return filesize;
}
bool ReadFile(const std::string &filePath, uint8_t *buffer, size_t bufferSize, bool checkSize)
{
if (!CheckInputFileValid(filePath, "bin")) {
ERROR_LOG("Check file: %s failed", filePath.c_str());
return false;
}
if (buffer == nullptr) {
ERROR_LOG("The buffer is null.");
return false;
}
size_t fileSize = GetFileSize(filePath);
if (checkSize && fileSize != bufferSize) {
ERROR_LOG("The size of file %s is not correct", filePath.c_str());
return false;
}
size_t readSize = min(fileSize, bufferSize);
std::ifstream file(filePath, std::ios::binary);
if (file.fail()) {
ERROR_LOG("Can not open file [%s] for reading", filePath.c_str());
return false;
}
file.read(reinterpret_cast<char *>(buffer), readSize);
file.close();
return true;
}
size_t WriteBinary(const std::string &filename, const char *data, uint64_t length, std::ios_base::openmode mode)
{
std::string realPath = filename;
if (!CheckWriteFilePathValid(realPath)) {
return 0;
}
std::ofstream ofs(realPath, mode);
if (!ofs.is_open()) {
ERROR_LOG("can not open file: %s", realPath.c_str());
return 0;
}
if (!Chmod(realPath, SAVE_DATA_FILE_AUTHORITY)) {
WARN_LOG("chmod open file %s to %#o failed", realPath.c_str(), SAVE_DATA_FILE_AUTHORITY);
return 0;
}
ofs.write(data, static_cast<std::streamsize>(length));
if (!ofs.good()) {
return 0;
}
return length;
}
bool Chmod(const std::string &filePath, mode_t fileAuthority)
{
if (chmod(filePath.c_str(), fileAuthority) != 0) {
WARN_LOG("Path [%s] chmod failed.", filePath.c_str());
return false;
}
return true;
}
bool IsSoftLinkRecursively(const std::string &path)
{
std::string nonConstPath = path;
while (!nonConstPath.empty() && nonConstPath.back() == '/') {
nonConstPath.pop_back();
}
std::vector<std::string> dirs;
SplitString(nonConstPath, *PATH_SEP, dirs);
if (dirs.empty()) {
return false;
}
std::string current;
for (auto it = dirs.cbegin(); it != dirs.cend(); ++it) {
if (it == dirs.cbegin()) {
current = *it;
} else {
current.append(PATH_SEP + *it);
}
if (*it == "." || *it == ".." || (*it).empty()) {
continue;
}
if (IsSoftLink(current)) {
return true;
}
}
return false;
}
bool PathLenCheckValid(const std::string &checkPath)
{
if (checkPath.length() > DIR_NAME_LENGTH_LIMIT) {
return false;
}
std::vector<std::string> dirs;
SplitString(checkPath, *PATH_SEP, dirs);
for (const auto &it : dirs) {
if (it.length() > FILE_NAME_LENGTH_LIMIT) {
return false;
}
}
return true;
}
bool CheckOwnerPermission(std::string &path, std::string &msg)
{
struct stat fileStat{};
if (stat(path.c_str(), &fileStat) != 0) {
msg = "get file" + path + "permission error.";
return false;
}
if (IsRootUser()) {
return true;
}
if ((fileStat.st_mode & (S_IWOTH | S_IWGRP)) != 0) {
msg = path + " is writable by any other users or group users, which may cause security problems.";
WARN_LOG("%s", msg.c_str());
}
if (fileStat.st_uid == 0 || fileStat.st_uid == static_cast<uint32_t>(getuid())) {
return true;
}
msg = path + " is not owned by the current user, which may cause security problems.";
WARN_LOG("%s", msg.c_str());
return true;
}
bool CheckFileSizeValid(const std::string &path, size_t threshold)
{
struct stat fileStat{};
if (stat(path.c_str(), &fileStat) != 0 || !S_ISREG(fileStat.st_mode)) {
return false;
}
auto fileSize = GetFileSize(path);
return fileSize == 0 || fileSize <= threshold;
}
bool GetCurrentPath(std::string ¤tPath)
{
char buf[PATH_MAX] = {'\0'};
if (getcwd(buf, sizeof(buf)) == nullptr) {
WARN_LOG("Get current working dir failed.");
return false;
}
currentPath = std::string(buf);
return true;
}
bool CheckWriteFilePathValid(std::string &path)
{
if (path.find(PATH_SEP) == std::string::npos) {
std::string currentPath;
if (!GetCurrentPath(currentPath)) {
ERROR_LOG("Failed to get %s file work dir", path.c_str());
return false;
}
path = JoinPath({currentPath, path});
}
std::string fileDir = path;
if (!RollbackPath(fileDir, 1)) {
WARN_LOG("Failed to get %s file dir", path.c_str());
return false;
}
std::string file;
if (!GetLastFile(path, file)) {
WARN_LOG("Faile to get last file from %s", path.c_str());
return false;
}
std::string realPath = Realpath(fileDir);
if (realPath == "") {
WARN_LOG("Failed to get real path of %s", fileDir.c_str());
return false;
}
path = JoinPath({realPath, file});
return true;
}
std::string GetSoFromEnvVar(const std::string &soName)
{
char const *ldEnv = getenv("LD_LIBRARY_PATH");
if (ldEnv == nullptr) {
return "";
}
std::string pathFromEnv = ldEnv;
std::vector<std::string> envs;
SplitString(pathFromEnv, ':', envs);
for (const std::string &path : envs) {
std::string soPath = JoinPath({path, soName});
std::string realSoPath = Realpath(soPath);
if (realSoPath.empty()) {
continue;
}
return realSoPath;
}
return "";
}
bool CheckInputFileValid(const std::string &path, const std::string &fileType, size_t threshold, std::string paramName)
{
if (!paramName.empty()) { paramName = " " + paramName; }
std::string absPath = Path(path).PathCanonicalize().ToString();
if (absPath.empty()) {
ERROR_LOG("Input path %s can not get absolute path.", path.c_str());
return false;
}
std::string errorMsg;
if (!IsStringCharValid(absPath, errorMsg)) {
ERROR_LOG("Input parameter %s path contains %s, which is invalid", paramName.c_str(), errorMsg.c_str());
return false;
}
if (IsSoftLinkRecursively(absPath)) {
WARN_LOG("Input parameter %s path contains softlink, may cause security problems", paramName.c_str());
}
if (!PathLenCheckValid(absPath)) {
ERROR_LOG("Input parameter %s path length is too long.", paramName.c_str());
return false;
}
if (!IsExist(absPath)) {
ERROR_LOG("Input parameter %s path does not exist", paramName.c_str());
return false;
}
bool expectDir = (fileType == "dir");
bool gotDir = IsDir(absPath);
if (expectDir != gotDir) {
const char *printType = expectDir ? "dir" : "file";
ERROR_LOG("Input parameter %s path: %s is not a %s", paramName.c_str(), absPath.c_str(), printType);
return false;
}
auto filePermission = GetFilePermission();
if (filePermission.count(fileType) == 0) {
ERROR_LOG("File type not in check map");
return false;
}
uint32_t fileMode = filePermission.at(fileType).first;
if (!CheckPathPermission(absPath, fileMode)) {
return false;
}
if (filePermission.at(fileType).second && !CheckOwnerPermission(absPath, errorMsg)) {
ERROR_LOG("%s", errorMsg.c_str());
return false;
}
if (!expectDir && (fileMode == S_IRUSR) && !CheckFileSizeValid(absPath, threshold)) {
ERROR_LOG("Input parameter %s file size is too large, max file size: %zu", paramName.c_str(), threshold);
return false;
}
return true;
}