* -------------------------------------------------------------------------
* 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 <string>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <map>
#include <cerrno>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/types.h>
#include <cstdlib>
#include <dirent.h>
#include <fcntl.h>
#include "include/ErrorCode.h"
#include "FileUtils.h"
namespace MindStudioDebugger {
namespace FileUtils {
using namespace MindStudioDebugger;
bool IsPathExist(const std::string& path)
{
struct stat buffer;
return (stat(path.c_str(), &buffer) == 0);
}
static std::string GetFullPath(const std::string &originPath)
{
if (originPath.empty()) {
return "";
}
if (originPath[0] == '/') {
return originPath;
}
std::string cwd;
char cwdBuf[PATH_MAX];
if (getcwd(cwdBuf, PATH_MAX) == nullptr) {
return "";
}
cwd = cwdBuf;
std::string fullPath = std::move(cwd + PATH_SEPARATOR + originPath);
return fullPath;
}
std::vector<std::string> SplitPath(const std::string &path, char separator)
{
std::vector<std::string> tokens;
size_t len = path.length();
size_t start = 0;
while (start < len) {
size_t end = path.find(separator, start);
if (end == std::string::npos) {
end = len;
}
if (start != end) {
tokens.push_back(path.substr(start, end - start));
}
start = end + 1;
}
return tokens;
}
std::string GetAbsPath(const std::string &originpath)
{
std::string fullPath = GetFullPath(originpath);
if (fullPath.empty()) {
return "";
}
std::vector<std::string> tokens = SplitPath(fullPath);
std::vector<std::string> tokensRefined;
for (std::string& token : tokens) {
if (token.empty() || token == ".") {
continue;
} else if (token == "..") {
if (tokensRefined.empty()) {
return "";
}
tokensRefined.pop_back();
} else {
tokensRefined.emplace_back(token);
}
}
if (tokensRefined.empty()) {
return "/";
}
std::string resolvedPath("");
for (std::string& token : tokensRefined) {
resolvedPath.append("/").append(token);
}
return resolvedPath;
}
bool IsDir(const std::string& path)
{
struct stat buffer;
if (stat(path.c_str(), &buffer) == 0) {
return (buffer.st_mode & S_IFDIR) != 0;
}
return false;
}
bool IsRegularFile(const std::string& path)
{
struct stat pathStat;
if (stat(path.c_str(), &pathStat) == 0) {
return S_ISREG(pathStat.st_mode);
}
return false;
}
bool IsFileSymbolLink(const std::string& path)
{
struct stat buffer;
if (lstat(path.c_str(), &buffer) == 0) {
if (S_ISLNK(buffer.st_mode)) {
return true;
}
}
return false;
}
bool IsPathCharactersValid(const std::string& path)
{
for (const char& ch : path) {
if (!std::isalnum(ch) && ch != '_' && ch != '.' && ch != ':' && ch != '/' && ch != '-') {
return false;
}
}
return true;
}
bool IsFileReadable(const std::string& path)
{
return access(path.c_str(), R_OK) == 0;
}
bool IsFileWritable(const std::string& path)
{
return access(path.c_str(), W_OK) == 0;
}
bool IsFileExecutable(const std::string& path)
{
return (access(path.c_str(), R_OK) == 0) && (access(path.c_str(), X_OK) == 0);
}
bool IsDirReadable(const std::string& path)
{
return (access(path.c_str(), R_OK) == 0) && (access(path.c_str(), X_OK) == 0);
}
std::string GetParentDir(const std::string& path)
{
size_t found = path.find_last_of('/');
if (found != std::string::npos) {
return path.substr(0, found);
}
return ".";
}
std::string GetFileName(const std::string& path)
{
size_t found = path.find_last_of('/');
if (found != std::string::npos) {
return path.substr(found + 1);
}
return path;
}
std::string GetFileBaseName(const std::string& path)
{
std::string fileName = GetFileName(path);
size_t dotPos = fileName.find_last_of('.');
if (dotPos != std::string::npos) {
return fileName.substr(0, dotPos);
}
return fileName;
}
std::string GetFileSuffix(const std::string& path)
{
std::string fileName = GetFileName(path);
size_t dotPos = fileName.find_last_of('.');
if (dotPos != std::string::npos && dotPos + 1 < fileName.size()) {
return fileName.substr(dotPos + 1);
}
return "";
}
bool CheckFileRWX(const std::string& path, const std::string& permissions)
{
if (permissions.find('r') != std::string::npos && !IsFileReadable(path)) {
return false;
}
if (permissions.find('w') != std::string::npos && !IsFileWritable(path)) {
return false;
}
if (permissions.find('x') != std::string::npos && !IsFileExecutable(path)) {
return false;
}
return true;
}
bool IsPathLengthLegal(const std::string& path)
{
if (path.length() > FULL_PATH_LENGTH_MAX || path.length() == 0) {
return false;
}
std::vector<std::string> tokens = SplitPath(path);
for (auto token : tokens) {
if (token.length() > FILE_NAME_LENGTH_MAX) {
return false;
}
}
return true;
}
bool IsPathDepthValid(const std::string& path)
{
auto depth = static_cast<uint32_t>(std::count(path.begin(), path.end(), PATH_SEPARATOR));
return depth <= PATH_DEPTH_MAX;
}
bool IsFileOwner(const std::string& path)
{
struct stat fileStat;
if (stat(path.c_str(), &fileStat) == 0) {
if (fileStat.st_uid == getuid()) {
return true;
}
}
return false;
}
DebuggerErrno DeleteFile(const std::string &path)
{
if (!IsPathExist(path)) {
return DebuggerErrno::OK;
}
if (IsFileSymbolLink(path)) {
return DebuggerErrno::ERROR_NOT_ALLOW_SOFTLINK;
}
if (remove(path.c_str()) == 0) {
return DebuggerErrno::OK;
} else {
return DebuggerErrno::ERROR_SYSCALL_FAILED;
}
}
static DebuggerErrno DeleteDirRec(const std::string &path, uint32_t depth)
{
if (depth > PATH_DEPTH_MAX) {
return DebuggerErrno::ERROR_PATH_TOO_DEEP;
}
DebuggerErrno ret;
DIR* dir = opendir(path.c_str());
if (dir == nullptr) {
return DebuggerErrno::ERROR_SYSCALL_FAILED;
}
struct dirent* entry;
while ((entry = readdir(dir)) != nullptr) {
if (strcmp(entry->d_name, ".") == 0 || (strcmp(entry->d_name, "..") == 0)) {
continue;
}
std::string entryPath = path + "/" + entry->d_name;
if (entry->d_type == DT_DIR) {
ret = DeleteDirRec(entryPath, depth + 1);
if (ret != DebuggerErrno::OK) {
closedir(dir);
return ret;
}
} else if (entry->d_type == DT_REG || entry->d_type == DT_LNK) {
if (remove(entryPath.c_str()) != 0) {
closedir(dir);
return DebuggerErrno::ERROR_SYSCALL_FAILED;
}
} else {
closedir(dir);
return DebuggerErrno::ERROR_ILLEGAL_FILE_TYPE;
}
}
closedir(dir);
if (rmdir(path.c_str()) != 0) {
if (errno == EACCES || errno == EROFS) {
return DebuggerErrno::ERROR_PERMISSION_DENINED;
} else {
return DebuggerErrno::ERROR_SYSCALL_FAILED;
}
}
return DebuggerErrno::OK;
}
DebuggerErrno DeleteDir(const std::string &path, bool recursion)
{
if (!IsPathExist(path)) {
return DebuggerErrno::OK;
}
if (IsFileSymbolLink(path)) {
return DebuggerErrno::ERROR_NOT_ALLOW_SOFTLINK;
}
if (recursion) {
return DeleteDirRec(path, 0);
}
if (rmdir(path.c_str()) != 0) {
return DebuggerErrno::ERROR_SYSCALL_FAILED;
}
return DebuggerErrno::OK;
}
static DebuggerErrno CreateDirAux(const std::string& path, bool recursion, mode_t mode)
{
std::string parent = GetParentDir(path);
DebuggerErrno ret;
if (!IsPathExist(parent)) {
if (!recursion) {
return DebuggerErrno::ERROR_DIR_NOT_EXISTS;
}
ret = CreateDirAux(parent, recursion, mode);
if (ret != DebuggerErrno::OK) {
return ret;
}
}
if (mkdir(path.c_str(), mode) != 0) {
if (errno == EACCES || errno == EROFS) {
return DebuggerErrno::ERROR_PERMISSION_DENINED;
} else {
return DebuggerErrno::ERROR_SYSCALL_FAILED;
}
}
return DebuggerErrno::OK;
}
DebuggerErrno CreateDir(const std::string &path, bool recursion, mode_t mode)
{
if (IsPathExist(path)) {
return DebuggerErrno::OK;
}
std::string realPath = GetAbsPath(path);
if (realPath.empty()) {
return DebuggerErrno::ERROR_CANNOT_PARSE_PATH;
}
if (!IsPathLengthLegal(realPath)) {
return DebuggerErrno::ERROR_PATH_TOO_LOOG;
}
if (!IsPathCharactersValid(realPath)) {
return DebuggerErrno::ERROR_PATH_CONTAINS_INVALID_CHAR;
}
if (!IsPathDepthValid(realPath)) {
return DebuggerErrno::ERROR_PATH_TOO_DEEP;
}
return CreateDirAux(realPath, recursion, mode);
}
DebuggerErrno Chmod(const std::string& path, const mode_t& mode)
{
if (!IsPathExist(path)) {
return DebuggerErrno::ERROR_FILE_NOT_EXISTS;
}
if (IsFileSymbolLink(path)) {
return DebuggerErrno::ERROR_NOT_ALLOW_SOFTLINK;
}
std::string absPath = GetAbsPath(path);
if (absPath.empty()) {
return DebuggerErrno::ERROR_CANNOT_PARSE_PATH;
}
return chmod(absPath.c_str(), mode) == 0 ? DebuggerErrno::OK : DebuggerErrno::ERROR_SYSCALL_FAILED;
}
DebuggerErrno GetFileSize(const std::string &path, size_t& size)
{
struct stat pathStat;
if (stat(path.c_str(), &pathStat) != 0) {
return DebuggerErrno::ERROR_FILE_NOT_EXISTS;
}
if (!S_ISREG(pathStat.st_mode)) {
return DebuggerErrno::ERROR_ILLEGAL_FILE_TYPE;
}
size = static_cast<size_t>(pathStat.st_size);
return DebuggerErrno::OK;
}
DebuggerErrno OpenFile(const std::string& path, std::ifstream& ifs, std::ios::openmode mode)
{
std::string realPath = GetAbsPath(path);
DebuggerErrno ret = CheckFileBeforeRead(realPath);
if (ret != DebuggerErrno::OK) {
return ret;
}
std::ifstream tmpifs(realPath, mode);
if (!tmpifs.is_open()) {
return DebuggerErrno::ERROR_FAILED_TO_OPEN_FILE;
}
ifs = std::move(tmpifs);
return DebuggerErrno::OK;
}
DebuggerErrno OpenFile(const std::string& path, std::ofstream& ofs, std::ios::openmode mode, mode_t permission)
{
DebuggerErrno ret;
std::string realPath = GetAbsPath(path);
if (realPath.empty()) {
return DebuggerErrno::ERROR_CANNOT_PARSE_PATH;
}
std::string parent = GetParentDir(realPath);
ret = CheckFileBeforeCreateOrWrite(realPath, true);
if (ret != DebuggerErrno::OK) {
return ret;
}
if (!IsPathExist(parent)) {
ret = CreateDir(parent, true);
if (ret != DebuggerErrno::OK) {
return ret;
}
}
if (!IsPathExist(realPath)) {
int fd = open(realPath.c_str(), O_CREAT | O_WRONLY, permission);
if (fd < 0) {
return DebuggerErrno::ERROR_FAILED_TO_OPEN_FILE;
}
close(fd);
}
std::ofstream tmpofs(realPath, mode);
if (!tmpofs.is_open()) {
return DebuggerErrno::ERROR_FAILED_TO_OPEN_FILE;
}
ofs = std::move(tmpofs);
return DebuggerErrno::OK;
}
DebuggerErrno CheckFileSuffixAndSize(const std::string &path, FileType type)
{
static const std::map<FileType, std::pair<std::string, size_t>> FileTypeCheckTbl = {
{FileType::PKL, {"kpl", MAX_PKL_SIZE}},
{FileType::NUMPY, {"npy", MAX_NUMPY_SIZE}},
{FileType::JSON, {"json", MAX_JSON_SIZE}},
{FileType::PT, {"pt", MAX_PT_SIZE}},
{FileType::CSV, {"csv", MAX_CSV_SIZE}},
{FileType::YAML, {"yaml", MAX_YAML_SIZE}},
};
size_t size;
DebuggerErrno ret = GetFileSize(path, size);
if (ret != DebuggerErrno::OK) {
return ret;
}
if (type == FileType::COMMON) {
if (size > MAX_FILE_SIZE_DEFAULT) {
return DebuggerErrno::ERROR_FILE_TOO_LARGE;
}
return DebuggerErrno::OK;
}
auto iter = FileTypeCheckTbl.find(type);
if (iter == FileTypeCheckTbl.end()) {
return DebuggerErrno::ERROR_UNKNOWN_FILE_SUFFIX;
}
std::string suffix = GetFileSuffix(path);
if (suffix != iter->second.first) {
return DebuggerErrno::ERROR_UNKNOWN_FILE_SUFFIX;
}
if (size > iter->second.second) {
return DebuggerErrno::ERROR_FILE_TOO_LARGE;
}
return DebuggerErrno::OK;
}
DebuggerErrno CheckDirCommon(const std::string &path)
{
std::string realPath = GetAbsPath(path);
if (realPath.empty()) {
return DebuggerErrno::ERROR_CANNOT_PARSE_PATH;
}
if (!IsPathExist(realPath)) {
return DebuggerErrno::ERROR_FILE_NOT_EXISTS;
}
if (!IsDir(realPath)) {
return DebuggerErrno::ERROR_ILLEGAL_FILE_TYPE;
}
if (!IsPathLengthLegal(realPath)) {
return DebuggerErrno::ERROR_PATH_TOO_LOOG;
}
if (!IsPathCharactersValid(realPath)) {
return DebuggerErrno::ERROR_PATH_CONTAINS_INVALID_CHAR;
}
if (!IsPathDepthValid(realPath)) {
return DebuggerErrno::ERROR_PATH_TOO_DEEP;
}
if (IsFileSymbolLink(path)) {
return DebuggerErrno::ERROR_NOT_ALLOW_SOFTLINK;
}
if (!IsDirReadable(path)) {
return DebuggerErrno::ERROR_PERMISSION_DENINED;
}
return DebuggerErrno::OK;
}
DebuggerErrno CheckFileBeforeRead(const std::string &path, const std::string& authority, FileType type)
{
std::string realPath = GetAbsPath(path);
if (realPath.empty()) {
return DebuggerErrno::ERROR_CANNOT_PARSE_PATH;
}
if (!IsPathExist(realPath)) {
return DebuggerErrno::ERROR_FILE_NOT_EXISTS;
}
if (!IsPathLengthLegal(realPath)) {
return DebuggerErrno::ERROR_PATH_TOO_LOOG;
}
if (!IsPathCharactersValid(realPath)) {
return DebuggerErrno::ERROR_PATH_CONTAINS_INVALID_CHAR;
}
if (!IsPathDepthValid(realPath)) {
return DebuggerErrno::ERROR_PATH_TOO_DEEP;
}
if (IsFileSymbolLink(realPath)) {
return DebuggerErrno::ERROR_NOT_ALLOW_SOFTLINK;
}
if (!CheckFileRWX(realPath, authority)) {
return DebuggerErrno::ERROR_PERMISSION_DENINED;
}
return CheckFileSuffixAndSize(path, type);
}
DebuggerErrno CheckFileBeforeCreateOrWrite(const std::string &path, bool overwrite)
{
std::string realPath = GetAbsPath(path);
if (realPath.empty()) {
return DebuggerErrno::ERROR_CANNOT_PARSE_PATH;
}
if (!IsPathLengthLegal(realPath)) {
return DebuggerErrno::ERROR_PATH_TOO_LOOG;
}
if (!IsPathCharactersValid(realPath)) {
return DebuggerErrno::ERROR_PATH_CONTAINS_INVALID_CHAR;
}
if (!IsPathDepthValid(realPath)) {
return DebuggerErrno::ERROR_PATH_TOO_DEEP;
}
if (IsPathExist(realPath)) {
if (!overwrite) {
return DebuggerErrno::ERROR_FILE_ALREADY_EXISTS;
}
if (!IsFileWritable(realPath) || !IsFileOwner(realPath)) {
return DebuggerErrno::ERROR_PERMISSION_DENINED;
}
}
return DebuggerErrno::OK;
}
}
}