1/*
2 * Copyright (c) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include "kernel_interface.h"
17
18#include <climits>
19#include <csignal>
20#include <dirent.h>
21#include <fstream>
22#include <regex>
23#include <securec.h>
24#include <sstream>
25#include <sys/stat.h>
26#include <unistd.h>
27
28#include "directory_ex.h"
29#include "file_ex.h"
30#include "memmgr_log.h"
31
32namespace OHOS {
33namespace Memory {
34namespace {
35const std::string TAG = "KernelInterface";
36}
37
38IMPLEMENT_SINGLE_INSTANCE(KernelInterface);
39
40const std::string KernelInterface::ROOT_PROC_PATH = "/proc";
41const std::string KernelInterface::MEMCG_BASE_PATH = "/dev/memcg";
42const std::string KernelInterface::FILE_MEMCG_PROCS = "cgroup.procs";
43
44#ifdef USE_HYPERHOLD_MEMORY
45const std::string KernelInterface::ZWAPD_PRESSURE_SHOW_PATH = "/dev/memcg/memory.zswapd_pressure_show";
46const std::string KernelInterface::ZWAPD_PRESSURE_SHOW_BUFFER_SIZE = "buffer_size";
47#else
48const std::string KernelInterface::ZWAPD_PRESSURE_SHOW_PATH = "/proc/meminfo";
49const std::string KernelInterface::ZWAPD_PRESSURE_SHOW_BUFFER_SIZE = "MemAvailable";
50#endif
51const std::string KernelInterface::MEMINFO_PATH = "/proc/meminfo";
52const std::string KernelInterface::FILE_PROC_STATUS = "status";
53const std::string KernelInterface::TOTAL_MEMORY = "MemTotal";
54
55bool KernelInterface::EchoToPath(const char* path, const char* content)
56{
57    int fd = open(path, O_WRONLY);
58    if (fd == -1) {
59        HILOGE("echo %{public}s > %{public}s failed: file is not open", content, path);
60        return false;
61    }
62    if (write(fd, content, strlen(content)) < 0) {
63        HILOGE("echo %{public}s > %{public}s failed: write failed", content, path);
64        close(fd);
65        return false;
66    }
67    close(fd);
68    HILOGI("echo %{public}s > %{public}s", content, path);
69    return true;
70}
71
72bool KernelInterface::IsFileExists(const std::string& path)
73{
74    if (path.empty()) {
75        return false;
76    }
77    struct stat st = {};
78    if (stat(path.c_str(), &st) == 0 && S_ISREG(st.st_mode)) {
79        return true;
80    }
81    return false;
82}
83
84bool KernelInterface::CreateFile(const std::string& path, const mode_t& mode)
85{
86    if (path.empty()) {
87        return false;
88    }
89    if (IsFileExists(path)) {
90        return true;
91    }
92
93    std::ofstream fout(path);
94    if (!fout.is_open()) {
95        return false;
96    }
97    fout.flush();
98    fout.close();
99    if (chmod(path.c_str(), mode) != 0) {
100        return false;
101    }
102
103    return true;
104}
105
106bool KernelInterface::CreateFile(const std::string& path)
107{
108    return CreateFile(path, FILE_MODE_644);
109}
110
111bool KernelInterface::RemoveFile(const std::string& path)
112{
113    return OHOS::RemoveFile(path);
114}
115
116bool KernelInterface::WriteToFile(const std::string& path, const std::string& content, bool truncated)
117{
118    int fd;
119    char actualPath[PATH_MAX + 1] = {0};
120    char* ptrRet = NULL;
121
122    if (strlen(path.c_str()) == 0 || strlen(path.c_str()) > PATH_MAX) {
123        HILOGE("file path is invalid");
124        return false;
125    }
126    ptrRet = realpath(path.c_str(), actualPath);
127    if (!ptrRet) {
128        HILOGE("file path cannot be canonicalized");
129        return false;
130    }
131    HILOGD("path:%{public}s, actualPath:%{public}s", path.c_str(), actualPath);
132    fd = open(actualPath, O_RDWR | (truncated ? O_TRUNC : O_APPEND));
133    if (fd == -1) {
134        HILOGE("echo %{public}s %{public}s %{public}s failed: file is not open",
135            content.c_str(), truncated ? ">" : ">>", path.c_str());
136        ptrRet = NULL;
137        return false;
138    }
139    if (write(fd, content.c_str(), strlen(content.c_str())) < 0) {
140        HILOGE("echo %{public}s %{public}s %{public}s failed: write failed",
141            content.c_str(), truncated ? ">" : ">>", path.c_str());
142        ptrRet = NULL;
143        close(fd);
144        return false;
145    }
146    ptrRet = NULL;
147    close(fd);
148    HILOGD("echo %{public}s %{public}s %{public}s", content.c_str(), truncated ? ">" : ">>", path.c_str());
149    return true;
150}
151
152bool KernelInterface::ReadFromFile(const std::string& path, std::string& content)
153{
154    return OHOS::LoadStringFromFile(path, content);
155}
156
157bool KernelInterface::ReadLinesFromFile(const std::string& path, std::vector<std::string>& lines)
158{
159    if (!IsFileExists(path)) {
160        HILOGE("no such file: %{public}s", path.c_str());
161        return false;
162    }
163    std::string line;
164    std::ifstream inf(path, std::ifstream::in);
165    if (!inf) {
166        HILOGE("ifstream(%{public}s) failed", path.c_str());
167        return false;
168    }
169    lines.clear();
170    while (!inf.eof()) {
171        getline(inf, line);
172        lines.push_back(line);
173    }
174    inf.close();
175    return true;
176}
177
178bool KernelInterface::IsDirExists(const std::string& path)
179{
180    if (path.empty()) {
181        return false;
182    }
183    struct stat st = {};
184    if (stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) {
185        return true;
186    }
187    return false;
188}
189
190int64_t KernelInterface::GetSystemCurTime()
191{
192    return std::chrono::duration_cast<std::chrono::milliseconds>
193        (std::chrono::system_clock::now().time_since_epoch()).count();
194}
195
196int64_t KernelInterface::GetSystemTimeMs()
197{
198    return std::chrono::duration_cast<std::chrono::seconds>
199        (std::chrono::system_clock::now().time_since_epoch()).count();
200}
201
202bool KernelInterface::IsExists(const std::string& path)
203{
204    return OHOS::FileExists(path);
205}
206
207bool KernelInterface::IsEmptyDir(const std::string& path)
208{
209    return OHOS::IsEmptyFolder(path);
210}
211
212bool KernelInterface::CreateDir(const std::string& path)
213{
214    return OHOS::ForceCreateDirectory(path); // default mode 755
215}
216
217bool KernelInterface::RemoveDirRecursively(const std::string& path)
218{
219    return OHOS::ForceRemoveDirectory(path) || (remove(path.c_str()) == 0);
220}
221
222std::string KernelInterface::RmDelimiter(const std::string& path)
223{
224    if (path.empty()) {
225        return path;
226    }
227    return OHOS::ExcludeTrailingPathDelimiter(path);
228}
229
230std::string KernelInterface::AddDelimiter(const std::string& path)
231{
232    if (path.empty()) {
233        return path;
234    }
235    return OHOS::IncludeTrailingPathDelimiter(path);
236}
237
238std::string KernelInterface::JoinPath(const std::string& prefixPath, const std::string& subPath)
239{
240    return AddDelimiter(prefixPath) + subPath;
241}
242
243std::string KernelInterface::JoinPath(const std::string& prefixPath, const std::string& midPath,
244                                      const std::string& subPath)
245{
246    return JoinPath(JoinPath(prefixPath, midPath), subPath);
247}
248
249bool KernelInterface::GetPidProcInfo(struct ProcInfo &procInfo)
250{
251    HILOGD("called!");
252
253    std::string statPath = JoinPath("/proc/", std::to_string(procInfo.pid), "/stat");
254
255    // format like:
256    // 1 (init) S 0 0 0 0 -1 4210944 1 ...
257    std::string stat;
258    std::string statm;
259    std::string statPid;
260    std::string vss;
261    std::string rss;
262    if (!ReadFromFile(statPath, stat)) {
263        HILOGD("stat file error!");
264        return false;
265    }
266    std::istringstream isStat(stat);
267    isStat >> statPid >> procInfo.name >> procInfo.status;
268
269    if (statPid != std::to_string(procInfo.pid)) {
270        HILOGD("pid error!");
271        return false;
272    }
273
274    std::string statmPath = JoinPath("/proc/", std::to_string(procInfo.pid), "/statm");
275    // format like:
276    // 640 472 369 38 0 115 0
277    if (!ReadFromFile(statmPath, statm)) {
278        HILOGD("statm file error!");
279        return false;
280    }
281    std::istringstream isStatm(statm);
282    isStatm >> vss >> rss; // pages
283    int rssValue = 0;
284    try {
285        rssValue = std::stoi(rss);
286    } catch (...) {
287        HILOGE("stoi(%{public}s) failed!", rss.c_str());
288        return false;
289    }
290
291    if (rssValue < 0 || rssValue > INT_MAX / PAGE_TO_KB) {
292        HILOGE("rssValue=%{public}d, rss is less than 0 or overflow!", rssValue);
293        return false;
294    }
295    procInfo.size = rssValue * PAGE_TO_KB;
296    HILOGI("GetProcInfo success: name is %{public}s, status is %{public}s, size = %{public}d KB",
297           procInfo.name.c_str(), procInfo.status.c_str(), procInfo.size);
298    return true;
299}
300
301bool KernelInterface::GetProcNameByPid(int pid, std::string &name)
302{
303    std::string statusPath = JoinPath("/proc/", std::to_string(pid), "/status");
304    std::string statusContent;
305    std::string nameTag;
306    if (!ReadFromFile(statusPath, statusContent)) {
307        HILOGE("status file [%{public}s] error!", statusPath.c_str());
308        return false;
309    }
310    std::istringstream statusStream(statusContent);
311    statusStream >> nameTag >> name;
312    return true;
313}
314
315void KernelInterface::ReadZswapdPressureShow(std::map<std::string, std::string>& result)
316{
317    std::string contentStr;
318    if (!ReadFromFile(ZWAPD_PRESSURE_SHOW_PATH, contentStr)) {
319        HILOGE("read %{public}s faild, content=[%{public}s]", ZWAPD_PRESSURE_SHOW_PATH.c_str(), contentStr.c_str());
320        return;
321    }
322    char *contentPtr = new char[contentStr.size() + 1];
323    if (contentPtr == nullptr) {
324        HILOGE("alloc buffer fail");
325        return;
326    }
327    if (strcpy_s(contentPtr, contentStr.size() + 1, contentStr.c_str()) != EOK) {
328        HILOGE("copy fail");
329        delete [] contentPtr;
330        return;
331    }
332    char *restPtr;
333    char *line = strtok_r(contentPtr, "\n", &restPtr);
334    do {
335        for (size_t i = 0; i < strlen(line); i++) {
336            if (line[i] == ':') {
337                line[i] = ' ';
338            }
339        }
340        std::string lineStr(line);
341        std::istringstream is(lineStr);
342        std::string name;
343        std::string value;
344        is >> name >> value;
345        result.insert(std::make_pair(name, value));
346
347        line = strtok_r(NULL, "\n", &restPtr);
348    } while (line);
349    if (restPtr) {
350        delete [] restPtr;
351    }
352    if (contentPtr) {
353        delete [] contentPtr;
354    }
355    return;
356}
357
358int KernelInterface::GetCurrentBuffer()
359{
360    std::map<std::string, std::string> result;
361    ReadZswapdPressureShow(result);
362    auto value = result.find(ZWAPD_PRESSURE_SHOW_BUFFER_SIZE);
363    if (value != result.end()) {
364#ifdef USE_HYPERHOLD_MEMORY
365        HILOGD("buffer_size=%{public}s MB", result[ZWAPD_PRESSURE_SHOW_BUFFER_SIZE].c_str());
366        return atoi(result[ZWAPD_PRESSURE_SHOW_BUFFER_SIZE].c_str()) * KB_PER_MB;
367#else
368        HILOGD("buffer_size=%{public}s KB", result[ZWAPD_PRESSURE_SHOW_BUFFER_SIZE].c_str());
369        return atoi(result[ZWAPD_PRESSURE_SHOW_BUFFER_SIZE].c_str());
370#endif
371    }
372    return MAX_BUFFER_KB;
373}
374
375int KernelInterface::KillOneProcessByPid(int pid)
376{
377    HILOGD("called! pid=%{public}d", pid);
378    struct ProcInfo procInfo;
379    int freedBuffer = 0;
380    procInfo.pid = pid;
381
382    if (!GetPidProcInfo(procInfo)) {
383        HILOGE("GetPidProcInfo fail !!!");
384        goto out;
385    }
386
387    if (procInfo.status == "D") {
388        HILOGE("Task %{public}s is at D status!", procInfo.name.c_str());
389        goto out;
390    }
391
392    if (kill(pid, SIGKILL)) {
393        HILOGE("Kill %{public}s errno=%{public}d!", procInfo.name.c_str(), errno);
394    }
395    HILOGE("%{public}s has been Killed ! (pid=%{public}d, freedSize=%{public}d KB)",
396        procInfo.name.c_str(), procInfo.pid, procInfo.size);
397
398    freedBuffer = procInfo.size;
399out:
400    return freedBuffer;
401}
402
403bool KernelInterface::GetAllProcPids(std::vector<unsigned int> &pids)
404{
405    pids.clear();
406    DIR *dir = opendir(ROOT_PROC_PATH.c_str());
407    if (dir == nullptr) {
408        HILOGE("dir %{public}s is not exist", ROOT_PROC_PATH.c_str());
409        return false;
410    }
411    struct dirent *ptr = nullptr;
412    while ((ptr = readdir(dir)) != nullptr) {
413        if ((strcmp(ptr->d_name, ".") == 0) || (strcmp(ptr->d_name, "..") == 0)) {
414            // current dir OR parent dir
415            continue;
416        } else if (ptr->d_type == DT_DIR) {
417            int pid = atoi(ptr->d_name);
418            if (pid > 0) {
419                pids.push_back((unsigned int)pid);
420            }
421        }
422    }
423    if (dir) {
424        closedir(dir);
425    }
426    HILOGD("there are %{public}zu pids under %{public}s", pids.size(), ROOT_PROC_PATH.c_str());
427    return true;
428}
429
430bool KernelInterface::GetUidByPid(unsigned int pid, unsigned int& uid)
431{
432    std::string path = JoinPath(ROOT_PROC_PATH, std::to_string(pid), "status");
433    std::string content;
434    if (!ReadFromFile(path, content)) {
435        HILOGE("read file failed. %{public}s", path.c_str());
436        return false;
437    }
438    content = std::regex_replace(content, std::regex("\n+"), " "); // replace \n with space
439    std::regex re(".*Uid:[[:s:]]*([[:d:]]+)[[:s:]]*([[:d:]]+)[[:s:]]*([[:d:]]+)[[:s:]]*([[:d:]]+).*");
440    std::smatch res;
441    if (!std::regex_match(content, res, re)) {
442        HILOGD("re not match. %{public}s", content.c_str());
443        return false;
444    }
445    try {
446        uid = (unsigned int)std::stoi(res.str(1)); // 1: Uid index
447    } catch (...) {
448        HILOGE("stoi(%{public}s) failed", res.str(1).c_str());
449        return false;
450    }
451    return true;
452}
453
454void DeleteCharArrayIfNotNull(char * charArray)
455{
456    if (charArray) {
457        delete [] charArray;
458        charArray = nullptr;
459    }
460}
461
462bool KernelInterface::ReadSwapOutKBSinceKernelBoot(const std::string &path, const std::string &tagStr,
463    unsigned long long &ret)
464{
465    std::string contentStr;
466    if (!ReadFromFile(path, contentStr)) {
467        return false;
468    }
469    char *contentPtr = new char[contentStr.size() + 1];
470    if (contentPtr == nullptr) {
471        HILOGE("alloc buffer fail");
472        return false;
473    }
474    if (strcpy_s(contentPtr, contentStr.size() + 1, contentStr.c_str()) != EOK) {
475        HILOGE("copy fail");
476        DeleteCharArrayIfNotNull(contentPtr);
477        return false;
478    }
479    bool success = false;
480    char *restPtr;
481    char *line = strtok_r(contentPtr, "\n", &restPtr);
482    do {
483        std::string lineStr(line);
484
485        size_t i = 0;
486        for (; i < strlen(line); i++) {
487            if (line[i] == ':') {
488                break;
489            }
490        }
491        if (i >= strlen(line) - 2) { // 2: no : in the line or : is at end of line
492            line = strtok_r(NULL, "\n", &restPtr);
493            continue;
494        }
495        std::string tag = lineStr.substr(0, i);
496        if (tag == tagStr) {
497            std::string value = lineStr.substr(i + 1);
498            std::istringstream iss(value);
499            std::string sizeStr;
500            std::string unitStr;
501            iss >> sizeStr >> unitStr;
502            try {
503                ret = std::strtoull(sizeStr.c_str(), nullptr, 10); // 10:Decimal
504                success = true;
505            } catch (...) {
506                HILOGE("parse [%{public}s] to unsigned long long error!", sizeStr.c_str());
507            }
508            break;
509        }
510
511        line = strtok_r(NULL, "\n", &restPtr);
512    } while (line);
513    DeleteCharArrayIfNotNull(contentPtr);
514    return success;
515}
516
517int KernelInterface::ParseMeminfo(const std::string &contentStr, const std::string &itemName)
518{
519    char *contentPtr = new (std::nothrow) char[contentStr.size() + 1];
520    if (contentPtr == nullptr) {
521        HILOGE("alloc buffer fail");
522        return -1;
523    }
524    if (strcpy_s(contentPtr, contentStr.size() + 1, contentStr.c_str()) != EOK) {
525        HILOGE("copy fail");
526        delete [] contentPtr;
527        return -1;
528    }
529    char *restPtr = nullptr;
530    char *line = strtok_r(contentPtr, "\n", &restPtr);
531    std::string name;
532    std::string value;
533    bool findTotalMem = false;
534    do {
535        for (size_t i = 0; i < strlen(line); i++) {
536            if (line[i] == ':') {
537                line[i] = ' ';
538            }
539        }
540        std::string lineStr(line);
541        std::istringstream is(lineStr);
542
543        is >> name >> value;
544        if (name == itemName) {
545            findTotalMem = true;
546            break;
547        }
548        line = strtok_r(NULL, "\n", &restPtr);
549    } while (line);
550    if (contentPtr) {
551        delete [] contentPtr;
552    }
553
554    if (findTotalMem == false) {
555        return -1;
556    }
557    std::string valueTemp = "";
558    for (auto c : value) {
559        if (c >= '0' && c <= '9') {
560            valueTemp = valueTemp + c;
561        }
562    }
563    return atoi(valueTemp.c_str());
564}
565
566int KernelInterface::GetTotalBuffer()
567{
568    if (totalBuffer_ >= 0) {
569        return totalBuffer_;
570    }
571
572    std::string contentStr;
573    if (!ReadFromFile(MEMINFO_PATH, contentStr)) {
574        HILOGE("read %{public}s faild, content=[%{public}s]", MEMINFO_PATH.c_str(), contentStr.c_str());
575        return -1;
576    }
577    totalBuffer_ = ParseMeminfo(contentStr, TOTAL_MEMORY);
578    return totalBuffer_;
579}
580
581bool KernelInterface::GetMemcgPids(const std::string &memcgPath, std::vector<int> &memcgPids)
582{
583    std::string path = JoinPath(memcgPath, FILE_MEMCG_PROCS);
584    std::vector<std::string> strLines;
585    if (!ReadLinesFromFile(path, strLines)) {
586        HILOGE("read file and split to lines failed : %{public}s", path.c_str());
587        return false;
588    }
589
590    memcgPids.clear();
591    int pid;
592    for (auto &it : strLines) {
593        try {
594            pid = stoi(it);
595        } catch (...) {
596            continue;
597        }
598        memcgPids.emplace_back(pid);
599    }
600    HILOGD("there are %{public}zu pids in %{public}s", memcgPids.size(), path.c_str());
601    return true;
602}
603
604bool KernelInterface::GetAllUserIds(std::vector<int> &userIds)
605{
606    userIds.clear();
607    DIR *dir = opendir(MEMCG_BASE_PATH.c_str());
608    if (dir == nullptr) {
609        HILOGE("dir %{public}s is not exist", MEMCG_BASE_PATH.c_str());
610        return false;
611    }
612    struct dirent *ptr = nullptr;
613    while ((ptr = readdir(dir)) != nullptr) {
614        if ((strcmp(ptr->d_name, ".") == 0) || (strcmp(ptr->d_name, "..") == 0)) {
615            // current dir OR parent dir
616            continue;
617        } else if (ptr->d_type == DT_DIR) {
618            int userId = atoi(ptr->d_name);
619            if (userId > 0) {
620                userIds.push_back(userId);
621            }
622        }
623    }
624    if (dir) {
625        closedir(dir);
626    }
627    HILOGD("there are %{public}zu userIds under %{public}s", userIds.size(), MEMCG_BASE_PATH.c_str());
628    return true;
629}
630
631void KernelInterface::SplitOneLineByDelim(const std::string &input, const char delimiter,
632    std::vector<std::string> &res)
633{
634    std::stringstream ss(input);
635    std::string temp;
636    while (getline(ss, temp, delimiter)) {
637        if (!temp.empty()) {
638            res.emplace_back(temp);
639        }
640    }
641}
642
643void KernelInterface::SplitOneLineByBlank(const std::string &input, std::vector<std::string> &res)
644{
645    std::stringstream ss(input);
646    std::string temp;
647    while (ss >> temp) {
648        if (!temp.empty()) {
649            res.emplace_back(temp);
650        }
651    }
652}
653} // namespace Memory
654} // namespace OHOS
655