/* * Copyright (c) 2022 Huawei Device Co., Ltd. * 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 "kernel_interface.h" #include #include #include #include #include #include #include #include #include #include "directory_ex.h" #include "file_ex.h" #include "memmgr_log.h" namespace OHOS { namespace Memory { namespace { const std::string TAG = "KernelInterface"; } IMPLEMENT_SINGLE_INSTANCE(KernelInterface); const std::string KernelInterface::ROOT_PROC_PATH = "/proc"; const std::string KernelInterface::MEMCG_BASE_PATH = "/dev/memcg"; const std::string KernelInterface::FILE_MEMCG_PROCS = "cgroup.procs"; #ifdef USE_HYPERHOLD_MEMORY const std::string KernelInterface::ZWAPD_PRESSURE_SHOW_PATH = "/dev/memcg/memory.zswapd_pressure_show"; const std::string KernelInterface::ZWAPD_PRESSURE_SHOW_BUFFER_SIZE = "buffer_size"; #else const std::string KernelInterface::ZWAPD_PRESSURE_SHOW_PATH = "/proc/meminfo"; const std::string KernelInterface::ZWAPD_PRESSURE_SHOW_BUFFER_SIZE = "MemAvailable"; #endif const std::string KernelInterface::MEMINFO_PATH = "/proc/meminfo"; const std::string KernelInterface::FILE_PROC_STATUS = "status"; const std::string KernelInterface::TOTAL_MEMORY = "MemTotal"; bool KernelInterface::EchoToPath(const char* path, const char* content) { int fd = open(path, O_WRONLY); if (fd == -1) { HILOGE("echo %{public}s > %{public}s failed: file is not open", content, path); return false; } if (write(fd, content, strlen(content)) < 0) { HILOGE("echo %{public}s > %{public}s failed: write failed", content, path); close(fd); return false; } close(fd); HILOGI("echo %{public}s > %{public}s", content, path); return true; } bool KernelInterface::IsFileExists(const std::string& path) { if (path.empty()) { return false; } struct stat st = {}; if (stat(path.c_str(), &st) == 0 && S_ISREG(st.st_mode)) { return true; } return false; } bool KernelInterface::CreateFile(const std::string& path, const mode_t& mode) { if (path.empty()) { return false; } if (IsFileExists(path)) { return true; } std::ofstream fout(path); if (!fout.is_open()) { return false; } fout.flush(); fout.close(); if (chmod(path.c_str(), mode) != 0) { return false; } return true; } bool KernelInterface::CreateFile(const std::string& path) { return CreateFile(path, FILE_MODE_644); } bool KernelInterface::RemoveFile(const std::string& path) { return OHOS::RemoveFile(path); } bool KernelInterface::WriteToFile(const std::string& path, const std::string& content, bool truncated) { int fd; char actualPath[PATH_MAX + 1] = {0}; char* ptrRet = NULL; if (strlen(path.c_str()) == 0 || strlen(path.c_str()) > PATH_MAX) { HILOGE("file path is invalid"); return false; } ptrRet = realpath(path.c_str(), actualPath); if (!ptrRet) { HILOGE("file path cannot be canonicalized"); return false; } HILOGD("path:%{public}s, actualPath:%{public}s", path.c_str(), actualPath); fd = open(actualPath, O_RDWR | (truncated ? O_TRUNC : O_APPEND)); if (fd == -1) { HILOGE("echo %{public}s %{public}s %{public}s failed: file is not open", content.c_str(), truncated ? ">" : ">>", path.c_str()); ptrRet = NULL; return false; } if (write(fd, content.c_str(), strlen(content.c_str())) < 0) { HILOGE("echo %{public}s %{public}s %{public}s failed: write failed", content.c_str(), truncated ? ">" : ">>", path.c_str()); ptrRet = NULL; close(fd); return false; } ptrRet = NULL; close(fd); HILOGD("echo %{public}s %{public}s %{public}s", content.c_str(), truncated ? ">" : ">>", path.c_str()); return true; } bool KernelInterface::ReadFromFile(const std::string& path, std::string& content) { return OHOS::LoadStringFromFile(path, content); } bool KernelInterface::ReadLinesFromFile(const std::string& path, std::vector& lines) { if (!IsFileExists(path)) { HILOGE("no such file: %{public}s", path.c_str()); return false; } std::string line; std::ifstream inf(path, std::ifstream::in); if (!inf) { HILOGE("ifstream(%{public}s) failed", path.c_str()); return false; } lines.clear(); while (!inf.eof()) { getline(inf, line); lines.push_back(line); } inf.close(); return true; } bool KernelInterface::IsDirExists(const std::string& path) { if (path.empty()) { return false; } struct stat st = {}; if (stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) { return true; } return false; } int64_t KernelInterface::GetSystemCurTime() { return std::chrono::duration_cast (std::chrono::system_clock::now().time_since_epoch()).count(); } int64_t KernelInterface::GetSystemTimeMs() { return std::chrono::duration_cast (std::chrono::system_clock::now().time_since_epoch()).count(); } bool KernelInterface::IsExists(const std::string& path) { return OHOS::FileExists(path); } bool KernelInterface::IsEmptyDir(const std::string& path) { return OHOS::IsEmptyFolder(path); } bool KernelInterface::CreateDir(const std::string& path) { return OHOS::ForceCreateDirectory(path); // default mode 755 } bool KernelInterface::RemoveDirRecursively(const std::string& path) { return OHOS::ForceRemoveDirectory(path) || (remove(path.c_str()) == 0); } std::string KernelInterface::RmDelimiter(const std::string& path) { if (path.empty()) { return path; } return OHOS::ExcludeTrailingPathDelimiter(path); } std::string KernelInterface::AddDelimiter(const std::string& path) { if (path.empty()) { return path; } return OHOS::IncludeTrailingPathDelimiter(path); } std::string KernelInterface::JoinPath(const std::string& prefixPath, const std::string& subPath) { return AddDelimiter(prefixPath) + subPath; } std::string KernelInterface::JoinPath(const std::string& prefixPath, const std::string& midPath, const std::string& subPath) { return JoinPath(JoinPath(prefixPath, midPath), subPath); } bool KernelInterface::GetPidProcInfo(struct ProcInfo &procInfo) { HILOGD("called!"); std::string statPath = JoinPath("/proc/", std::to_string(procInfo.pid), "/stat"); // format like: // 1 (init) S 0 0 0 0 -1 4210944 1 ... std::string stat; std::string statm; std::string statPid; std::string vss; std::string rss; if (!ReadFromFile(statPath, stat)) { HILOGD("stat file error!"); return false; } std::istringstream isStat(stat); isStat >> statPid >> procInfo.name >> procInfo.status; if (statPid != std::to_string(procInfo.pid)) { HILOGD("pid error!"); return false; } std::string statmPath = JoinPath("/proc/", std::to_string(procInfo.pid), "/statm"); // format like: // 640 472 369 38 0 115 0 if (!ReadFromFile(statmPath, statm)) { HILOGD("statm file error!"); return false; } std::istringstream isStatm(statm); isStatm >> vss >> rss; // pages int rssValue = 0; try { rssValue = std::stoi(rss); } catch (...) { HILOGE("stoi(%{public}s) failed!", rss.c_str()); return false; } if (rssValue < 0 || rssValue > INT_MAX / PAGE_TO_KB) { HILOGE("rssValue=%{public}d, rss is less than 0 or overflow!", rssValue); return false; } procInfo.size = rssValue * PAGE_TO_KB; HILOGI("GetProcInfo success: name is %{public}s, status is %{public}s, size = %{public}d KB", procInfo.name.c_str(), procInfo.status.c_str(), procInfo.size); return true; } bool KernelInterface::GetProcNameByPid(int pid, std::string &name) { std::string statusPath = JoinPath("/proc/", std::to_string(pid), "/status"); std::string statusContent; std::string nameTag; if (!ReadFromFile(statusPath, statusContent)) { HILOGE("status file [%{public}s] error!", statusPath.c_str()); return false; } std::istringstream statusStream(statusContent); statusStream >> nameTag >> name; return true; } void KernelInterface::ReadZswapdPressureShow(std::map& result) { std::string contentStr; if (!ReadFromFile(ZWAPD_PRESSURE_SHOW_PATH, contentStr)) { HILOGE("read %{public}s faild, content=[%{public}s]", ZWAPD_PRESSURE_SHOW_PATH.c_str(), contentStr.c_str()); return; } char *contentPtr = new char[contentStr.size() + 1]; if (contentPtr == nullptr) { HILOGE("alloc buffer fail"); return; } if (strcpy_s(contentPtr, contentStr.size() + 1, contentStr.c_str()) != EOK) { HILOGE("copy fail"); delete [] contentPtr; return; } char *restPtr; char *line = strtok_r(contentPtr, "\n", &restPtr); do { for (size_t i = 0; i < strlen(line); i++) { if (line[i] == ':') { line[i] = ' '; } } std::string lineStr(line); std::istringstream is(lineStr); std::string name; std::string value; is >> name >> value; result.insert(std::make_pair(name, value)); line = strtok_r(NULL, "\n", &restPtr); } while (line); if (restPtr) { delete [] restPtr; } if (contentPtr) { delete [] contentPtr; } return; } int KernelInterface::GetCurrentBuffer() { std::map result; ReadZswapdPressureShow(result); auto value = result.find(ZWAPD_PRESSURE_SHOW_BUFFER_SIZE); if (value != result.end()) { #ifdef USE_HYPERHOLD_MEMORY HILOGD("buffer_size=%{public}s MB", result[ZWAPD_PRESSURE_SHOW_BUFFER_SIZE].c_str()); return atoi(result[ZWAPD_PRESSURE_SHOW_BUFFER_SIZE].c_str()) * KB_PER_MB; #else HILOGD("buffer_size=%{public}s KB", result[ZWAPD_PRESSURE_SHOW_BUFFER_SIZE].c_str()); return atoi(result[ZWAPD_PRESSURE_SHOW_BUFFER_SIZE].c_str()); #endif } return MAX_BUFFER_KB; } int KernelInterface::KillOneProcessByPid(int pid) { HILOGD("called! pid=%{public}d", pid); struct ProcInfo procInfo; int freedBuffer = 0; procInfo.pid = pid; if (!GetPidProcInfo(procInfo)) { HILOGE("GetPidProcInfo fail !!!"); goto out; } if (procInfo.status == "D") { HILOGE("Task %{public}s is at D status!", procInfo.name.c_str()); goto out; } if (kill(pid, SIGKILL)) { HILOGE("Kill %{public}s errno=%{public}d!", procInfo.name.c_str(), errno); } HILOGE("%{public}s has been Killed ! (pid=%{public}d, freedSize=%{public}d KB)", procInfo.name.c_str(), procInfo.pid, procInfo.size); freedBuffer = procInfo.size; out: return freedBuffer; } bool KernelInterface::GetAllProcPids(std::vector &pids) { pids.clear(); DIR *dir = opendir(ROOT_PROC_PATH.c_str()); if (dir == nullptr) { HILOGE("dir %{public}s is not exist", ROOT_PROC_PATH.c_str()); return false; } struct dirent *ptr = nullptr; while ((ptr = readdir(dir)) != nullptr) { if ((strcmp(ptr->d_name, ".") == 0) || (strcmp(ptr->d_name, "..") == 0)) { // current dir OR parent dir continue; } else if (ptr->d_type == DT_DIR) { int pid = atoi(ptr->d_name); if (pid > 0) { pids.push_back((unsigned int)pid); } } } if (dir) { closedir(dir); } HILOGD("there are %{public}zu pids under %{public}s", pids.size(), ROOT_PROC_PATH.c_str()); return true; } bool KernelInterface::GetUidByPid(unsigned int pid, unsigned int& uid) { std::string path = JoinPath(ROOT_PROC_PATH, std::to_string(pid), "status"); std::string content; if (!ReadFromFile(path, content)) { HILOGE("read file failed. %{public}s", path.c_str()); return false; } content = std::regex_replace(content, std::regex("\n+"), " "); // replace \n with space std::regex re(".*Uid:[[:s:]]*([[:d:]]+)[[:s:]]*([[:d:]]+)[[:s:]]*([[:d:]]+)[[:s:]]*([[:d:]]+).*"); std::smatch res; if (!std::regex_match(content, res, re)) { HILOGD("re not match. %{public}s", content.c_str()); return false; } try { uid = (unsigned int)std::stoi(res.str(1)); // 1: Uid index } catch (...) { HILOGE("stoi(%{public}s) failed", res.str(1).c_str()); return false; } return true; } void DeleteCharArrayIfNotNull(char * charArray) { if (charArray) { delete [] charArray; charArray = nullptr; } } bool KernelInterface::ReadSwapOutKBSinceKernelBoot(const std::string &path, const std::string &tagStr, unsigned long long &ret) { std::string contentStr; if (!ReadFromFile(path, contentStr)) { return false; } char *contentPtr = new char[contentStr.size() + 1]; if (contentPtr == nullptr) { HILOGE("alloc buffer fail"); return false; } if (strcpy_s(contentPtr, contentStr.size() + 1, contentStr.c_str()) != EOK) { HILOGE("copy fail"); DeleteCharArrayIfNotNull(contentPtr); return false; } bool success = false; char *restPtr; char *line = strtok_r(contentPtr, "\n", &restPtr); do { std::string lineStr(line); size_t i = 0; for (; i < strlen(line); i++) { if (line[i] == ':') { break; } } if (i >= strlen(line) - 2) { // 2: no : in the line or : is at end of line line = strtok_r(NULL, "\n", &restPtr); continue; } std::string tag = lineStr.substr(0, i); if (tag == tagStr) { std::string value = lineStr.substr(i + 1); std::istringstream iss(value); std::string sizeStr; std::string unitStr; iss >> sizeStr >> unitStr; try { ret = std::strtoull(sizeStr.c_str(), nullptr, 10); // 10:Decimal success = true; } catch (...) { HILOGE("parse [%{public}s] to unsigned long long error!", sizeStr.c_str()); } break; } line = strtok_r(NULL, "\n", &restPtr); } while (line); DeleteCharArrayIfNotNull(contentPtr); return success; } int KernelInterface::ParseMeminfo(const std::string &contentStr, const std::string &itemName) { char *contentPtr = new (std::nothrow) char[contentStr.size() + 1]; if (contentPtr == nullptr) { HILOGE("alloc buffer fail"); return -1; } if (strcpy_s(contentPtr, contentStr.size() + 1, contentStr.c_str()) != EOK) { HILOGE("copy fail"); delete [] contentPtr; return -1; } char *restPtr = nullptr; char *line = strtok_r(contentPtr, "\n", &restPtr); std::string name; std::string value; bool findTotalMem = false; do { for (size_t i = 0; i < strlen(line); i++) { if (line[i] == ':') { line[i] = ' '; } } std::string lineStr(line); std::istringstream is(lineStr); is >> name >> value; if (name == itemName) { findTotalMem = true; break; } line = strtok_r(NULL, "\n", &restPtr); } while (line); if (contentPtr) { delete [] contentPtr; } if (findTotalMem == false) { return -1; } std::string valueTemp = ""; for (auto c : value) { if (c >= '0' && c <= '9') { valueTemp = valueTemp + c; } } return atoi(valueTemp.c_str()); } int KernelInterface::GetTotalBuffer() { if (totalBuffer_ >= 0) { return totalBuffer_; } std::string contentStr; if (!ReadFromFile(MEMINFO_PATH, contentStr)) { HILOGE("read %{public}s faild, content=[%{public}s]", MEMINFO_PATH.c_str(), contentStr.c_str()); return -1; } totalBuffer_ = ParseMeminfo(contentStr, TOTAL_MEMORY); return totalBuffer_; } bool KernelInterface::GetMemcgPids(const std::string &memcgPath, std::vector &memcgPids) { std::string path = JoinPath(memcgPath, FILE_MEMCG_PROCS); std::vector strLines; if (!ReadLinesFromFile(path, strLines)) { HILOGE("read file and split to lines failed : %{public}s", path.c_str()); return false; } memcgPids.clear(); int pid; for (auto &it : strLines) { try { pid = stoi(it); } catch (...) { continue; } memcgPids.emplace_back(pid); } HILOGD("there are %{public}zu pids in %{public}s", memcgPids.size(), path.c_str()); return true; } bool KernelInterface::GetAllUserIds(std::vector &userIds) { userIds.clear(); DIR *dir = opendir(MEMCG_BASE_PATH.c_str()); if (dir == nullptr) { HILOGE("dir %{public}s is not exist", MEMCG_BASE_PATH.c_str()); return false; } struct dirent *ptr = nullptr; while ((ptr = readdir(dir)) != nullptr) { if ((strcmp(ptr->d_name, ".") == 0) || (strcmp(ptr->d_name, "..") == 0)) { // current dir OR parent dir continue; } else if (ptr->d_type == DT_DIR) { int userId = atoi(ptr->d_name); if (userId > 0) { userIds.push_back(userId); } } } if (dir) { closedir(dir); } HILOGD("there are %{public}zu userIds under %{public}s", userIds.size(), MEMCG_BASE_PATH.c_str()); return true; } void KernelInterface::SplitOneLineByDelim(const std::string &input, const char delimiter, std::vector &res) { std::stringstream ss(input); std::string temp; while (getline(ss, temp, delimiter)) { if (!temp.empty()) { res.emplace_back(temp); } } } void KernelInterface::SplitOneLineByBlank(const std::string &input, std::vector &res) { std::stringstream ss(input); std::string temp; while (ss >> temp) { if (!temp.empty()) { res.emplace_back(temp); } } } } // namespace Memory } // namespace OHOS