1 /*
2 * Copyright (c) 2024 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 "dump_runtime_helper.h"
17
18 #include <dfx_signal_handler.h>
19 #include <sys/statvfs.h>
20 #include <sys/stat.h>
21 #include <sys/xattr.h>
22 #include "app_mgr_client.h"
23 #include "faultloggerd_client.h"
24 #include "hilog_tag_wrapper.h"
25 #include "js_runtime.h"
26 #include "js_runtime_utils.h"
27 #include "singleton.h"
28 #include "dfx_jsnapi.h"
29 #include "parameters.h"
30 #include "ffrt.h"
31 #include "directory_ex.h"
32 #include "storage_acl.h"
33
34 namespace OHOS {
35 namespace AppExecFwk {
36 const char *MODULE_NAME = "hiviewdfx.jsLeakWatcher";
37 const char *CHECK = "check";
38 const char *REQUIRE_NAPI = "requireNapi";
39
40 static bool g_betaVersion = OHOS::system::GetParameter("const.logsystem.versiontype", "unknown") == "beta";
41 static bool g_developMode = (OHOS::system::GetParameter("persist.hiview.leak_detector", "unknown") == "enable") ||
42 (OHOS::system::GetParameter("persist.hiview.leak_detector", "unknown") == "true");
43 static int g_oomDumpProcessQuota = 0;
44 static uint64_t g_lastOOMDumpTime = 0;
45 static constexpr const char* const EVENT_XATTR_NAME = "user.appevent";
46 static constexpr const char* const OOM_QUOTA_XATTR_NAME = "user.oomdump.quota";
47 static constexpr const char* const PROPERTY2C = "user.oomdumptelemetry.quota";
48 static constexpr const char* const HIAPPEVENT_PATH = "/data/storage/el2/base/cache/hiappevent";
49 static constexpr const char* const OOM_QUOTA_PATH = "/data/storage/el2/base/cache/rawheap";
50 static constexpr const char* const JS_HEAP_LOGTYPE = "user.event_config.js_heap_logtype";
51 static constexpr const char* const EVENT_RAWHEAP = "event_rawheap";
52 static constexpr uint64_t OOM_DUMP_INTERVAL = 7 * 24 * 60 * 60;
53 static constexpr uint64_t OOM_DUMP_SPACE_LIMIT = 30ull * 1024 * 1024 * 1024;
54 static constexpr uint32_t EVENT_RESOURCE_OVERLIMIT_MASK = 6;
55 static constexpr uint64_t BIT_MASK = 1;
56 static constexpr uint32_t BUF_SIZE_256 = 256;
57 static constexpr int DECIMAL_BASE = 10;
58 static constexpr int KB_PER_MB = 1024;
59
60 enum {
61 INDEX_DELIVERY_TS = 0,
62 INDEX_COMPRESSED_TS_0,
63 INDEX_COMPRESSED_TS_1,
64 INDEX_COMPRESSED_TS_2,
65 INDEX_COMPRESSED_TS_3,
66 INDEX_COMPRESSED_TS_4,
67 INDEX_APP_QUOTA,
68 INDEX_HAS_SENT,
69 INDEX_ROM_RSV_SIZE,
70 PROPERTY2C_SIZE
71 };
72
DumpRuntimeHelper(const std::shared_ptr<OHOSApplication> & application)73 DumpRuntimeHelper::DumpRuntimeHelper(const std::shared_ptr<OHOSApplication> &application)
74 : application_(application)
75 {}
76
Check2DOOMDumpOpt()77 bool DumpRuntimeHelper::Check2DOOMDumpOpt()
78 {
79 char* env = getenv("DFX_RESOURCE_OVERLIMIT_OPTIONS");
80 if (env == nullptr) {
81 return false;
82 }
83 return strstr(env, "oomdump:enable") != nullptr;
84 }
85
SetAppFreezeFilterCallback()86 void DumpRuntimeHelper::SetAppFreezeFilterCallback()
87 {
88 CreateDirDelay(OOM_QUOTA_PATH);
89 if (application_ == nullptr) {
90 TAG_LOGE(AAFwkTag::APPKIT, "null application");
91 return;
92 }
93 auto &runtime = application_->GetRuntime();
94 if (runtime == nullptr) {
95 TAG_LOGE(AAFwkTag::APPKIT, "null runtime");
96 return;
97 }
98 auto appfreezeFilterCallback =
99 [this] (const int32_t pid, const bool needDecreaseQuota, std::string &eventConfig) -> bool {
100 eventConfig = GetEventConfig(JS_HEAP_LOGTYPE);
101 auto client = DelayedSingleton<AppExecFwk::AppMgrClient>::GetInstance();
102 if (client == nullptr) {
103 TAG_LOGE(AAFwkTag::APPKIT, "null client");
104 return false;
105 }
106 if (!CheckOomdumpSwitch()) {
107 TAG_LOGE(AAFwkTag::APPKIT, "failed to check oomdump switch");
108 return false;
109 }
110 if (g_betaVersion || g_developMode || !needDecreaseQuota) {
111 TAG_LOGI(AAFwkTag::APPKIT, "no need to check quota, just dump."
112 " beta: %{public}d, develop: %{public}d, hidumper: %{public}d",
113 g_betaVersion, g_developMode, !needDecreaseQuota);
114 client->SetAppFreezeFilter(pid);
115 return true;
116 }
117 bool ret2custom = (eventConfig == EVENT_RAWHEAP);
118 bool ret2D = Check2DQuota(needDecreaseQuota);
119 bool ret2C = Check2CQuota();
120 if (!ret2custom && !ret2D && !ret2C) {
121 TAG_LOGI(AAFwkTag::APPKIT, "check custom|2C|2D quota both failed, no dump.");
122 return false;
123 }
124 TAG_LOGI(AAFwkTag::APPKIT, "check success, will dump. custom: %{public}d 2C: %{public}d, 2D: %{public}d",
125 ret2custom, ret2C, ret2D);
126 client->SetAppFreezeFilter(pid);
127 return true;
128 };
129 auto vm = (static_cast<AbilityRuntime::JsRuntime&>(*runtime)).GetEcmaVm();
130 panda::DFXJSNApi::SetAppFreezeFilterCallback(vm, appfreezeFilterCallback);
131 }
132
CheckOomdumpSwitch()133 bool DumpRuntimeHelper::CheckOomdumpSwitch()
134 {
135 // if value is disable, then no dump whatever
136 std::string switchValue = OHOS::system::GetParameter("hiview.oomdump.switch", "unknown");
137 TAG_LOGI(AAFwkTag::APPKIT, "oomdump switch: %{public}s", switchValue.c_str());
138 return switchValue != "disable";
139 }
140
Check2CQuota()141 bool DumpRuntimeHelper::Check2CQuota()
142 {
143 std::vector<int64_t> quota2C;
144 if (!GetQuota(OOM_QUOTA_PATH, PROPERTY2C, quota2C, PROPERTY2C_SIZE)) {
145 TAG_LOGE(AAFwkTag::APPKIT, "failed to GetQuota for PROPERTY2C");
146 return false;
147 }
148
149 int compressQuota = GetCompressQuota(quota2C);
150 int appQuota = static_cast<int>(quota2C[INDEX_APP_QUOTA]);
151 int leftQuota = MIN(compressQuota, appQuota) - static_cast<int>(quota2C[INDEX_HAS_SENT]);
152 if (leftQuota <= 0) {
153 TAG_LOGE(AAFwkTag::APPKIT, "invalid quota, compress: %{public}d, app: %{public}d, sent: %{public}" PRId64,
154 compressQuota, appQuota, quota2C[INDEX_HAS_SENT]);
155 return false;
156 }
157
158 uint64_t now = GetCurrentTimestamp();
159 if (quota2C[INDEX_DELIVERY_TS] < 0) {
160 TAG_LOGE(AAFwkTag::APPKIT, "invalid quota2C[%{public}d]: %{public}" PRId64,
161 INDEX_DELIVERY_TS, quota2C[INDEX_DELIVERY_TS]);
162 return false;
163 }
164 uint64_t deliveryTs = static_cast<uint64_t>(quota2C[INDEX_DELIVERY_TS]);
165 if (now < deliveryTs || now - deliveryTs > OOM_DUMP_INTERVAL) {
166 TAG_LOGE(AAFwkTag::APPKIT, "invalid deliveryTs: %{public}" PRIu64, deliveryTs);
167 return false;
168 }
169
170 uint64_t spaceQuota = static_cast<uint64_t>(quota2C[INDEX_ROM_RSV_SIZE]) * KB_PER_MB;
171 if (!CheckOOMFreeSpace(spaceQuota)) {
172 TAG_LOGE(AAFwkTag::APPKIT, "rom free space is less than spaceQuota: %{public}" PRIu64, spaceQuota);
173 return false;
174 }
175
176 TAG_LOGE(AAFwkTag::APPKIT, "success check 2C Quota");
177 return true;
178 }
179
Check2DQuota(bool needDecreaseQuota)180 bool DumpRuntimeHelper::Check2DQuota(bool needDecreaseQuota)
181 {
182 if (!Check2DOOMDumpOpt()) {
183 TAG_LOGE(AAFwkTag::APPKIT, "Check2DOOMDumpOpt failed");
184 return false;
185 }
186 uint64_t time = 0ull;
187 int quota = 0;
188 int value = OHOS::system::GetIntParameter("persist.hiview.oomdump.process.maxcount", 0);
189 if (value < 0) {
190 TAG_LOGE(AAFwkTag::APPKIT, "invalid persist.hiview.oomdump.process.maxcount: %{public}d", value);
191 return false;
192 }
193 uint32_t oomDumpProcessMaxQuota = static_cast<uint32_t>(value);
194 if (!Get2DQuota(OOM_QUOTA_PATH, OOM_QUOTA_XATTR_NAME, time, quota)) {
195 TAG_LOGI(AAFwkTag::APPKIT, "failed to Get2DQuota, need to Init2DOOMDumpQuota");
196 if (!Init2DOOMDumpQuota(OOM_QUOTA_PATH, oomDumpProcessMaxQuota)) {
197 TAG_LOGE(AAFwkTag::APPKIT, "failed Init2DOOMDumpQuota");
198 return false;
199 }
200 }
201 value = OHOS::system::GetIntParameter("persist.hiview.oomdump.maxcount", 0);
202 if (value < 0) {
203 TAG_LOGE(AAFwkTag::APPKIT, "invalid persist.hiview.oomdump.maxcount: %{public}d", value);
204 return false;
205 }
206 uint32_t oomDumpMaxQuota = static_cast<uint32_t>(value);
207 if (!Check2DOOMDumpQuota(oomDumpMaxQuota, oomDumpProcessMaxQuota)) {
208 TAG_LOGE(AAFwkTag::APPKIT, "Check2DOOMDumpQuota failed");
209 return false;
210 }
211 Set2DQuota(OOM_QUOTA_PATH, GetCurrentTimestamp(), g_oomDumpProcessQuota - 1);
212
213 TAG_LOGI(AAFwkTag::APPKIT, "succeed to Check2DQuota");
214 return true;
215 }
216
DumpJsHeap(const OHOS::AppExecFwk::JsHeapDumpInfo & info)217 void DumpRuntimeHelper::DumpJsHeap(const OHOS::AppExecFwk::JsHeapDumpInfo &info)
218 {
219 if (application_ == nullptr) {
220 TAG_LOGE(AAFwkTag::APPKIT, "null application");
221 return;
222 }
223 auto &runtime = application_->GetRuntime();
224 if (runtime == nullptr) {
225 TAG_LOGE(AAFwkTag::APPKIT, "null runtime");
226 return;
227 }
228 if (runtime->GetLanguage() != OHOS::AbilityRuntime::Runtime::Language::JS) {
229 TAG_LOGE(AAFwkTag::APPKIT, "runtime language is not JS");
230 return;
231 }
232 if (info.needLeakobj) {
233 std::string checkList = "";
234 GetCheckList(runtime, checkList);
235 WriteCheckList(checkList);
236 }
237
238 if (info.needSnapshot == true) {
239 runtime->DumpHeapSnapshot(info.tid, info.needGc, info.needBinary);
240 } else {
241 if (info.needGc == true) {
242 runtime->ForceFullGC(info.tid);
243 }
244 }
245 }
246
DumpCjHeap(const OHOS::AppExecFwk::CjHeapDumpInfo & info)247 void DumpRuntimeHelper::DumpCjHeap(const OHOS::AppExecFwk::CjHeapDumpInfo &info)
248 {
249 if (application_ == nullptr) {
250 TAG_LOGE(AAFwkTag::APPKIT, "null application");
251 return;
252 }
253 auto &runtime = application_->GetRuntime();
254 if (runtime == nullptr) {
255 TAG_LOGE(AAFwkTag::APPKIT, "null runtime");
256 return;
257 }
258 if (runtime->GetLanguage() != OHOS::AbilityRuntime::Runtime::Language::CJ) {
259 TAG_LOGE(AAFwkTag::APPKIT, "runtime language is not CJ");
260 return;
261 }
262 if (info.needSnapshot == true) {
263 runtime->DumpHeapSnapshot(info.pid, info.needGc);
264 } else {
265 if (info.needGc == true) {
266 runtime->ForceFullGC(info.pid);
267 }
268 }
269 }
270
GetCheckList(const std::unique_ptr<AbilityRuntime::Runtime> & runtime,std::string & checkList)271 void DumpRuntimeHelper::GetCheckList(const std::unique_ptr<AbilityRuntime::Runtime> &runtime, std::string &checkList)
272 {
273 if (runtime->GetLanguage() != AbilityRuntime::Runtime::Language::JS) {
274 TAG_LOGE(AAFwkTag::APPKIT, "current language not js");
275 return;
276 }
277 AbilityRuntime::JsRuntime &jsruntime = static_cast<AbilityRuntime::JsRuntime&>(*runtime);
278 AbilityRuntime::HandleScope handleScope(jsruntime);
279 auto env = jsruntime.GetNapiEnv();
280
281 napi_value global = nullptr;
282 napi_get_global(env, &global);
283 napi_value requireValue = GetJsLeakModule(env, global);
284 if (requireValue == nullptr) {
285 TAG_LOGE(AAFwkTag::APPKIT, "null requireValue");
286 return;
287 }
288 napi_value result = GetMethodCheck(env, requireValue, global);
289 if (result == nullptr) {
290 TAG_LOGE(AAFwkTag::APPKIT, "null result");
291 return;
292 }
293
294 size_t checkListSize = 0;
295 napi_get_value_string_utf8(env, result, nullptr, 0, &checkListSize);
296 checkList.resize(checkListSize + 1);
297 napi_get_value_string_utf8(env, result, &checkList[0], checkListSize + 1, &checkListSize);
298 }
299
GetJsLeakModule(napi_env env,napi_value global)300 napi_value DumpRuntimeHelper::GetJsLeakModule(napi_env env, napi_value global)
301 {
302 napi_value napiFunc = nullptr;
303 napi_status status = napi_get_named_property(env, global, REQUIRE_NAPI, &napiFunc);
304 if (status != napi_ok) {
305 TAG_LOGE(AAFwkTag::APPKIT, "fail, %{public}d", status);
306 return nullptr;
307 }
308 napi_value moduleName = nullptr;
309 napi_create_string_utf8(env, MODULE_NAME, strlen(MODULE_NAME), &moduleName);
310 napi_value param[1] = {moduleName};
311 napi_value requireValue = nullptr;
312 status = napi_call_function(env, global, napiFunc, 1, ¶m[0], &requireValue);
313 if (status != napi_ok) {
314 TAG_LOGE(AAFwkTag::APPKIT, "fail, %{public}d", status);
315 return nullptr;
316 }
317 return requireValue;
318 }
319
GetMethodCheck(napi_env env,napi_value requireValue,napi_value global)320 napi_value DumpRuntimeHelper::GetMethodCheck(napi_env env, napi_value requireValue, napi_value global)
321 {
322 napi_value methodCheck = nullptr;
323 napi_status status = napi_get_named_property(env, requireValue, CHECK, &methodCheck);
324 if (status != napi_ok) {
325 TAG_LOGE(AAFwkTag::APPKIT, "fail, %{public}d", status);
326 return nullptr;
327 }
328 napi_valuetype valuetype = napi_undefined;
329 status = napi_typeof(env, methodCheck, &valuetype);
330 if (status != napi_ok) {
331 TAG_LOGE(AAFwkTag::APPKIT, "failed, %{public}d", status);
332 return nullptr;
333 }
334 napi_value result = nullptr;
335 status = napi_call_function(env, global, methodCheck, 0, nullptr, &result);
336 if (status != napi_ok) {
337 TAG_LOGE(AAFwkTag::APPKIT, "fail, %{public}d", status);
338 return nullptr;
339 }
340 return result;
341 }
342
WriteCheckList(const std::string & checkList)343 void DumpRuntimeHelper::WriteCheckList(const std::string &checkList)
344 {
345 TAG_LOGD(AAFwkTag::APPKIT, "called");
346 int32_t fd = RequestFileDescriptor(static_cast<int32_t>(FaultLoggerType::JS_HEAP_LEAK_LIST));
347 if (fd < 0) {
348 TAG_LOGE(AAFwkTag::APPKIT, "fd:%{public}d.\n", fd);
349 return;
350 }
351 if (write(fd, checkList.c_str(), strlen(checkList.c_str())) == -1) {
352 TAG_LOGE(AAFwkTag::APPKIT, "fd:%{public}d, errno:%{public}d.\n", fd, errno);
353 close(fd);
354 return;
355 }
356 close(fd);
357 }
358
CreateDirDelay(const std::string & path)359 void DumpRuntimeHelper::CreateDirDelay(const std::string &path)
360 {
361 ffrt::submit([=] {
362 if (!CreateDir(path)) {
363 TAG_LOGE(AAFwkTag::APPKIT, "failed to create %{public}s", path.c_str());
364 return;
365 }
366 constexpr mode_t defaultLogDirMode = 0770;
367 if (!OHOS::ChangeModeDirectory(path.c_str(), defaultLogDirMode)) {
368 TAG_LOGE(AAFwkTag::APPKIT, "failed to changeMode %{public}s", path.c_str());
369 return;
370 }
371 if (OHOS::StorageDaemon::AclSetAccess(path, "g:1201:rwx") != 0) {
372 TAG_LOGE(AAFwkTag::APPKIT, "failed to AclSetAccess, path: %{public}s", path.c_str());
373 return;
374 }
375 TAG_LOGI(AAFwkTag::APPKIT, "success to AclSetAccess, path: %{public}s", path.c_str());
376 }, {}, {}, {ffrt::task_attr().name("ffrt_dfr_CreateDir")});
377 }
378
Init2DOOMDumpQuota(const std::string & path,uint32_t oomDumpProcessMaxQuota)379 bool DumpRuntimeHelper::Init2DOOMDumpQuota(const std::string &path, uint32_t oomDumpProcessMaxQuota)
380 {
381 uint64_t currentTime = GetCurrentTimestamp();
382 if (!Set2DQuota(path, currentTime, oomDumpProcessMaxQuota)) {
383 TAG_LOGE(AAFwkTag::APPKIT, "Set2DQuota failed, current: %{public}" PRIu64 ", quota: %{public}d",
384 currentTime, oomDumpProcessMaxQuota);
385 return false;
386 }
387 TAG_LOGI(AAFwkTag::APPKIT, "Init dumpTime: %{public}" PRIu64 ", quota: %{public}d.",
388 currentTime, oomDumpProcessMaxQuota);
389 return true;
390 }
391
CreateDir(const std::string & path)392 bool DumpRuntimeHelper::CreateDir(const std::string &path)
393 {
394 if (IsFileExists(path)) {
395 TAG_LOGI(AAFwkTag::APPKIT, "File existed. dir: %{public}s", path.c_str());
396 return true;
397 }
398 if (!ForceCreateDirectory(path)) {
399 TAG_LOGE(AAFwkTag::APPKIT, "Failed to create dir: %{public}s", path.c_str());
400 return false;
401 }
402 return true;
403 }
404
Check2DOOMDumpQuota(uint32_t oomDumpMaxQuota,uint32_t oomDumpProcessMaxQuota)405 bool DumpRuntimeHelper::Check2DOOMDumpQuota(uint32_t oomDumpMaxQuota, uint32_t oomDumpProcessMaxQuota)
406 {
407 if (!CheckAppListenedEvents(HIAPPEVENT_PATH)) {
408 TAG_LOGI(AAFwkTag::APPKIT, "Not subscribe oom event.");
409 return false;
410 }
411 if (oomDumpMaxQuota <= 0) {
412 TAG_LOGI(AAFwkTag::APPKIT, "The whole machine quota has been exhausted, quota=%{public}u", oomDumpMaxQuota);
413 return false;
414 }
415 if (!CheckOOMFreeSpace(OOM_DUMP_SPACE_LIMIT)) {
416 TAG_LOGI(AAFwkTag::APPKIT, "Device space is not enough.");
417 return false;
418 }
419 if (!Get2DQuota(OOM_QUOTA_PATH, OOM_QUOTA_XATTR_NAME, g_lastOOMDumpTime, g_oomDumpProcessQuota)) {
420 TAG_LOGE(AAFwkTag::APPKIT, "Check2DOOMDumpQuota failed to get quota.");
421 return false;
422 }
423 uint64_t currentTime = GetCurrentTimestamp();
424 if (currentTime - g_lastOOMDumpTime <= OOM_DUMP_INTERVAL) {
425 if (g_oomDumpProcessQuota <= 0) {
426 TAG_LOGI(AAFwkTag::APPKIT, "Weekly quota has been exhausted, quota=%{public}d", g_oomDumpProcessQuota);
427 return false;
428 }
429 } else {
430 TAG_LOGI(AAFwkTag::APPKIT, "Over one week, reset process quota=%{public}u", oomDumpProcessMaxQuota);
431 g_oomDumpProcessQuota = static_cast<int>(oomDumpProcessMaxQuota);
432 if (!Set2DQuota(OOM_QUOTA_PATH, currentTime, oomDumpProcessMaxQuota)) {
433 TAG_LOGE(AAFwkTag::APPKIT, "Failed to reset quota.");
434 return false;
435 }
436 }
437 TAG_LOGI(AAFwkTag::APPKIT, "Last oom time=%{public}" PRIu64 ", currentTime=%{public}" PRIu64 ", quota=%{public}d",
438 g_lastOOMDumpTime, currentTime, g_oomDumpProcessQuota);
439 return true;
440 }
441
GetQuota(const std::string & path,const std::string & property,std::vector<int64_t> & output,size_t size)442 bool DumpRuntimeHelper::GetQuota(const std::string &path, const std::string &property,
443 std::vector<int64_t> &output, size_t size)
444 {
445 std::string value;
446 if (!GetDirXattr(path, property, value)) {
447 TAG_LOGE(AAFwkTag::APPKIT, "Failed to get xattr. preperty: %{public}s", property.c_str());
448 return false;
449 }
450 size_t commaCount = static_cast<size_t>(std::count(value.begin(), value.end(), ','));
451 if (commaCount != size - 1) {
452 TAG_LOGE(AAFwkTag::APPKIT, "Invalid number of commas: %{public}zu", commaCount);
453 return false;
454 }
455
456 std::vector<int64_t> rtn;
457 std::stringstream ss(value);
458 std::string segment;
459 while (std::getline(ss, segment, ',')) {
460 if (segment.empty()) {
461 TAG_LOGE(AAFwkTag::APPKIT, "Invalid segment: empty value found, index: %{public}zu", rtn.size());
462 return false;
463 }
464 long long tmp;
465 if (!SafeStoll(segment, tmp)) {
466 TAG_LOGE(AAFwkTag::APPKIT, "failed to SafeStoll, segment: %{public}s", segment.c_str());
467 return false;
468 }
469 rtn.push_back(static_cast<int64_t>(tmp));
470 }
471
472 if (rtn.size() != size) {
473 TAG_LOGE(AAFwkTag::APPKIT, "Invalid number of entries: %{public}zu", rtn.size());
474 return false;
475 }
476
477 output = rtn;
478 return true;
479 }
480
Get2DQuota(const std::string & path,const std::string & property,uint64_t & timestamp,int & quota)481 bool DumpRuntimeHelper::Get2DQuota(const std::string &path, const std::string &property,
482 uint64_t ×tamp, int "a)
483 {
484 std::string value;
485 if (!GetDirXattr(path, property, value)) {
486 TAG_LOGE(AAFwkTag::APPKIT, "Failed to get xattr. path: %{public}s, preperty: %{public}s",
487 path.c_str(), property.c_str());
488 return false;
489 }
490 std::istringstream ss(value);
491 char delimiter;
492 if (!(ss >> timestamp >> delimiter >> quota) || delimiter != ',') {
493 TAG_LOGE(AAFwkTag::APPKIT, "Failed to decode value: %{public}s", value.c_str());
494 return false;
495 }
496 return true;
497 }
498
Set2DQuota(const std::string & path,uint64_t timestamp,int newQuota)499 bool DumpRuntimeHelper::Set2DQuota(const std::string &path, uint64_t timestamp, int newQuota)
500 {
501 std::ostringstream value;
502 value << timestamp << "," << newQuota;
503 if (newQuota < 0) {
504 TAG_LOGE(AAFwkTag::APPKIT, "Invalid quota.");
505 return false;
506 }
507 TAG_LOGI(AAFwkTag::APPKIT, "Set quota=%{public}d", newQuota);
508 return SetDirXattr(path, OOM_QUOTA_XATTR_NAME, value.str());
509 }
510
GetCurrentTimestamp()511 uint64_t DumpRuntimeHelper::GetCurrentTimestamp()
512 {
513 return static_cast<uint64_t>(time(nullptr));
514 }
515
CheckOOMFreeSpace(uint64_t maxSpace)516 bool DumpRuntimeHelper::CheckOOMFreeSpace(uint64_t maxSpace)
517 {
518 struct statvfs st;
519 if (statvfs("/data/storage/el2/base/", &st) != 0) {
520 return false;
521 }
522
523 unsigned long freeSize = st.f_bsize * st.f_bfree;
524 if (freeSize <= maxSpace) {
525 return false;
526 }
527 return true;
528 }
529
GetMaskFromDirXattr(const std::string & path)530 uint64_t DumpRuntimeHelper::GetMaskFromDirXattr(const std::string &path)
531 {
532 std::string value;
533 if (!GetDirXattr(path, EVENT_XATTR_NAME, value)) {
534 TAG_LOGE(AAFwkTag::APPKIT, "Failed to get subscirbe xattr.");
535 return 0;
536 }
537 return static_cast<uint64_t>(std::strtoull(value.c_str(), nullptr, 0));
538 }
539
CheckAppListenedEvents(const std::string & path)540 bool DumpRuntimeHelper::CheckAppListenedEvents(const std::string &path)
541 {
542 uint64_t eventsMask = GetMaskFromDirXattr(path);
543 if (eventsMask == 0) {
544 TAG_LOGE(AAFwkTag::APPKIT, "Failed to get events mask for path:%{public}s", path.c_str());
545 return false;
546 }
547 if (!(eventsMask & (BIT_MASK << EVENT_RESOURCE_OVERLIMIT_MASK))) {
548 TAG_LOGI(AAFwkTag::APPKIT, "Unlistened event for path:%{public}s", path.c_str());
549 return false;
550 }
551 return true;
552 }
553
IsFileExists(const std::string & file)554 bool DumpRuntimeHelper::IsFileExists(const std::string &file)
555 {
556 return access(file.c_str(), F_OK) == 0;
557 }
558
ForceCreateDirectory(const std::string & path)559 bool DumpRuntimeHelper::ForceCreateDirectory(const std::string &path)
560 {
561 std::string::size_type index = 0;
562 do {
563 std::string subPath;
564 index = path.find('/', index + 1); // (index + 1) means the next char traversed
565 if (index == std::string::npos) {
566 subPath = path;
567 } else {
568 subPath = path.substr(0, index);
569 }
570
571 if (!IsFileExists(subPath) && mkdir(subPath.c_str(), S_IRWXU) != 0) {
572 return false;
573 }
574 } while (index != std::string::npos);
575 return IsFileExists(path);
576 }
577
SetDirXattr(const std::string & path,const std::string & name,const std::string & value)578 bool DumpRuntimeHelper::SetDirXattr(const std::string &path, const std::string &name, const std::string &value)
579 {
580 return setxattr(path.c_str(), name.c_str(), value.c_str(), strlen(value.c_str()), 0) == 0;
581 }
582
GetDirXattr(const std::string & path,const std::string & name,std::string & value)583 bool DumpRuntimeHelper::GetDirXattr(const std::string &path, const std::string &name, std::string &value)
584 {
585 char buf[BUF_SIZE_256] = {0};
586 if (getxattr(path.c_str(), name.c_str(), buf, sizeof(buf) - 1) == -1) {
587 TAG_LOGE(AAFwkTag::APPKIT, "failed getxattr, name: %{public}s, err: %{public}d:%{public}s",
588 name.c_str(), errno, strerror(errno));
589 return false;
590 }
591 value = buf;
592 return true;
593 }
594
SafeStoll(const std::string & str,long long & value)595 bool DumpRuntimeHelper::SafeStoll(const std::string &str, long long &value)
596 {
597 value = 0;
598 size_t start = 0;
599 bool isNegative = false;
600
601 if (str.empty()) {
602 TAG_LOGE(AAFwkTag::APPKIT, "str is empty");
603 return false;
604 }
605
606 if (str[0] == '-') {
607 isNegative = true;
608 start = 1;
609 } else if (str[0] == '+') {
610 start = 1;
611 }
612
613 for (size_t i = start; i < str.size(); ++i) {
614 if (!isdigit(str[i])) {
615 TAG_LOGE(AAFwkTag::APPKIT, "digit check failed. str: %{public}s, index: %{public}zu", str.c_str(), i);
616 return false;
617 }
618 if (value > (LLONG_MAX - (str[i] - '0')) / DECIMAL_BASE) {
619 TAG_LOGE(AAFwkTag::APPKIT, "out of range, str: %{public}s", str.c_str());
620 return false;
621 }
622 value = value * DECIMAL_BASE + (str[i] - '0');
623 }
624
625 if (isNegative) {
626 value = -value;
627 }
628
629 TAG_LOGD(AAFwkTag::APPKIT, "success, str: %{public}s, result: %{public}lld", str.c_str(), value);
630 return true;
631 }
632
GetCompressQuota(const std::vector<int64_t> & quotas)633 int DumpRuntimeHelper::GetCompressQuota(const std::vector<int64_t> "as)
634 {
635 int ret = 0;
636 uint64_t now = static_cast<uint64_t>(time(nullptr));
637 for (int i = INDEX_COMPRESSED_TS_0; i <= INDEX_COMPRESSED_TS_4; i++) {
638 uint64_t compressTs = static_cast<uint64_t>(quotas[i]);
639 if (now > compressTs && now - compressTs > OOM_DUMP_INTERVAL) {
640 ret++;
641 }
642 }
643
644 return ret;
645 }
646
SplitPropertyByComma(const std::string & property,std::string & runningId,std::string & value)647 bool DumpRuntimeHelper::SplitPropertyByComma(const std::string &property, std::string &runningId, std::string &value)
648 {
649 size_t commaPos = property.find(',');
650 if (commaPos == std::string::npos) {
651 TAG_LOGE(AAFwkTag::APPKIT, "no comma in property");
652 return false;
653 }
654
655 runningId = property.substr(0, commaPos);
656 value = property.substr(commaPos + 1);
657 return true;
658 }
659
GetEventConfig(const std::string & key)660 std::string DumpRuntimeHelper::GetEventConfig(const std::string &key)
661 {
662 std::string property;
663 if (!GetDirXattr(OOM_QUOTA_PATH, key, property)) {
664 TAG_LOGE(AAFwkTag::APPKIT, "failed to GetDirXattr, key: %{public}s", key.c_str());
665 return "";
666 }
667 std::string customRunningId;
668 std::string value;
669 if (!SplitPropertyByComma(property, customRunningId, value)) {
670 TAG_LOGW(AAFwkTag::APPKIT, "failed to SplitPropertyByComma, property: %{public}s", property.c_str());
671 return "";
672 }
673 std::string curRunningId = DFX_GetAppRunningUniqueId();
674 if (curRunningId.empty()) {
675 TAG_LOGE(AAFwkTag::APPKIT, "curRunningId is empty");
676 return "";
677 }
678 if (customRunningId != curRunningId) {
679 TAG_LOGW(AAFwkTag::APPKIT, "runningId not match, customRunningId: %{public}s, curRunningId: %{public}s",
680 customRunningId.c_str(), curRunningId.c_str());
681 return "";
682 }
683
684 TAG_LOGI(AAFwkTag::APPKIT, "succeed to getEventConfig, value: %{public}s", value.c_str());
685 return value;
686 }
687 } // namespace AppExecFwk
688 } // namespace OHOS
689