1/* 2 * Copyright (c) 2021-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#include "bootevent.h" 16 17#include <stdbool.h> 18#include "init_module_engine.h" 19#include "init_group_manager.h" 20#include "init_cmdexecutor.h" 21#include "trigger_manager.h" 22#include "init_log.h" 23#include "plugin_adapter.h" 24#include "init_hook.h" 25#include "init_service.h" 26#include "bootstage.h" 27#include "securec.h" 28#include "init_utils.h" 29#include "init_cmds.h" 30#include "config_policy_utils.h" 31 32#ifdef WITH_SELINUX 33#include <policycoreutils.h> 34#endif 35 36static int GetBootSwitchEnable(const char *paramName) 37{ 38 char bootEventOpen[6] = ""; // 6 is length of bool value 39 uint32_t len = sizeof(bootEventOpen); 40 SystemReadParam(paramName, bootEventOpen, &len); 41 if (strcmp(bootEventOpen, "true") == 0 || strcmp(bootEventOpen, "1") == 0) { 42 return 1; 43 } 44 return 0; 45} 46 47static int g_bootEventNum = 0; 48 49static bool g_isBootCompleted = false; 50 51static ListNode bootEventList = {&bootEventList, &bootEventList}; 52 53static int BootEventParaListCompareProc(ListNode *node, void *data) 54{ 55 BOOT_EVENT_PARAM_ITEM *item = (BOOT_EVENT_PARAM_ITEM *)node; 56 if (strncmp(item->paramName, BOOT_EVENT_PARA_PREFIX, BOOT_EVENT_PARA_PREFIX_LEN) != 0) { 57 return -1; 58 } 59 if (strcmp(item->paramName + BOOT_EVENT_PARA_PREFIX_LEN, (const char *)data) == 0) { 60 return 0; 61 } 62 return -1; 63} 64 65static int ParseBooteventCompareProc(ListNode *node, void *data) 66{ 67 BOOT_EVENT_PARAM_ITEM *item = (BOOT_EVENT_PARAM_ITEM *)node; 68 if (strcmp(item->paramName, (const char *)data) == 0) { 69 return 0; 70 } 71 return -1; 72} 73 74static int AddBootEventItem(BOOT_EVENT_PARAM_ITEM *item, const char *paramName) 75{ 76 OH_ListInit(&item->node); 77 for (int i = 0; i < BOOTEVENT_MAX; i++) { 78 item->timestamp[i].tv_nsec = 0; 79 item->timestamp[i].tv_sec = 0; 80 } 81 item->paramName = strdup(paramName); 82 if (item->paramName == NULL) { 83 free(item); 84 return -1; 85 } 86 item->flags = BOOTEVENT_TYPE_SERVICE; 87 OH_ListAddTail(&bootEventList, (ListNode *)&item->node); 88 g_bootEventNum++; 89 return 0; 90} 91 92static int AddBootEventItemByName(const char *paramName) 93{ 94 BOOT_EVENT_PARAM_ITEM *item = (BOOT_EVENT_PARAM_ITEM *)calloc(1, sizeof(BOOT_EVENT_PARAM_ITEM)); 95 if (item == NULL) { 96 return -1; 97 } 98 99 return AddBootEventItem(item, paramName); 100} 101 102static void SetServiceBooteventHookMgr(const char *serviceName, const char *paramName, int state) 103{ 104#ifndef STARTUP_INIT_TEST 105 SERVICE_BOOTEVENT_CTX context; 106 context.serviceName = serviceName; 107 context.reserved = paramName; 108 context.state = state; 109 HookMgrExecute(GetBootStageHookMgr(), INIT_SERVICE_BOOTEVENT, (void*)(&context), NULL); 110#endif 111} 112 113 114static int AddServiceBootEvent(const char *serviceName, const char *paramName) 115{ 116 ServiceExtData *extData = NULL; 117 ListNode *found = NULL; 118 if ((paramName == NULL) || (strncmp(paramName, BOOT_EVENT_PARA_PREFIX, BOOT_EVENT_PARA_PREFIX_LEN) != 0)) { 119 return -1; 120 } 121 found = OH_ListFind(&bootEventList, (void *)paramName, ParseBooteventCompareProc); 122 if (found != NULL) { 123 return -1; 124 } 125 // Find an empty bootevent data position 126 for (int i = HOOK_ID_BOOTEVENT; i < HOOK_ID_BOOTEVENT_MAX; i++) { 127 extData = AddServiceExtData(serviceName, i, NULL, sizeof(BOOT_EVENT_PARAM_ITEM)); 128 if (extData != NULL) { 129 break; 130 } 131 } 132 133 INIT_CHECK(extData != NULL, return -1); 134 135 BOOT_EVENT_PARAM_ITEM *item = (BOOT_EVENT_PARAM_ITEM *)extData->data; 136 137 if (AddBootEventItem(item, paramName) != 0) { 138 DelServiceExtData(serviceName, extData->dataId); 139 return -1; 140 } 141 142 SetServiceBooteventHookMgr(serviceName, paramName, 1); 143 return 0; 144} 145 146static void AddInitBootEvent(const char *bootEventName) 147{ 148 BOOT_EVENT_PARAM_ITEM *found = NULL; 149 found = (BOOT_EVENT_PARAM_ITEM *)OH_ListFind(&bootEventList, (void *)bootEventName, ParseBooteventCompareProc); 150 if (found != NULL) { 151 (void)clock_gettime(CLOCK_MONOTONIC, &(found->timestamp[BOOTEVENT_READY])); 152 return; 153 } 154 155 BOOT_EVENT_PARAM_ITEM *item = calloc(1, sizeof(BOOT_EVENT_PARAM_ITEM)); 156 INIT_CHECK(item != NULL, return); 157 158 OH_ListInit(&item->node); 159 160 (void)clock_gettime(CLOCK_MONOTONIC, &(item->timestamp[BOOTEVENT_FORK])); 161 162 item->paramName = strdup(bootEventName); 163 INIT_CHECK(item->paramName != NULL, free(item); 164 return); 165 166 item->flags = BOOTEVENT_TYPE_JOB; 167 OH_ListAddTail(&bootEventList, (ListNode *)&item->node); 168 return; 169} 170 171#define BOOT_EVENT_BOOT_COMPLETED "bootevent.boot.completed" 172 173static void BootEventDestroy(ListNode *node) 174{ 175 BOOT_EVENT_PARAM_ITEM *bootEvent = (BOOT_EVENT_PARAM_ITEM *)node; 176 INIT_CHECK(bootEvent->paramName == NULL, free((void *)bootEvent->paramName)); 177 free((void *)bootEvent); 178} 179 180static int AddItemToJson(cJSON *root, const char *name, double startTime, int pid, double durTime) 181{ 182 cJSON *obj = cJSON_CreateObject(); // release obj at traverse done 183 INIT_CHECK_RETURN_VALUE(obj != NULL, -1); 184 cJSON_AddStringToObject(obj, "name", name); 185 cJSON_AddNumberToObject(obj, "ts", startTime); 186 cJSON_AddStringToObject(obj, "ph", "X"); 187 cJSON_AddNumberToObject(obj, "pid", pid); 188 cJSON_AddNumberToObject(obj, "tid", pid); 189 cJSON_AddNumberToObject(obj, "dur", durTime); 190 cJSON_AddItemToArray(root, obj); 191 return 0; 192} 193 194static int BootEventTraversal(ListNode *node, void *root) 195{ 196 static int start = 0; 197 BOOT_EVENT_PARAM_ITEM *item = (BOOT_EVENT_PARAM_ITEM *)node; 198 double forkTime = (double)item->timestamp[BOOTEVENT_FORK].tv_sec * MSECTONSEC + 199 (double)item->timestamp[BOOTEVENT_FORK].tv_nsec / USTONSEC; 200 double readyTime = (double)item->timestamp[BOOTEVENT_READY].tv_sec * MSECTONSEC + 201 (double)item->timestamp[BOOTEVENT_READY].tv_nsec / USTONSEC; 202 double durTime = readyTime - forkTime; 203 if (item->pid == 0) { 204 if (durTime < SAVEINITBOOTEVENTMSEC) { 205 return 0; 206 } 207 item->pid = 1; // 1 is init pid 208 } 209 if (start == 0) { 210 // set trace start time 0 211 INIT_CHECK_RETURN_VALUE(AddItemToJson((cJSON *)root, item->paramName, 0, 212 1, 0) == 0, -1); 213 start++; 214 } 215 INIT_CHECK_RETURN_VALUE(AddItemToJson((cJSON *)root, item->paramName, forkTime, 216 item->pid, durTime > 0 ? durTime : 0) == 0, -1); 217 return 0; 218} 219 220static int CreateBootEventFile(const char *file, mode_t mode) 221{ 222 if (access(file, F_OK) == 0) { 223 INIT_LOGW("File %s already exist", file); 224 return 0; 225 } 226 if (errno != ENOENT) { 227 INIT_LOGW("Failed to access %s, err = %d", file, errno); 228 return -1; 229 } 230 CheckAndCreateDir(file); 231 int fd = open(file, O_CREAT, mode); 232 if (fd < 0) { 233 INIT_LOGE("Failed create %s, err=%d", file, errno); 234 return -1; 235 } 236 close(fd); 237#ifdef WITH_SELINUX 238 INIT_LOGI("start to restorecon selinux"); 239 (void)RestoreconRecurse(BOOTEVENT_OUTPUT_PATH); 240#endif 241 return 0; 242} 243 244static int SaveServiceBootEvent() 245{ 246 INIT_CHECK(GetBootSwitchEnable("persist.init.bootuptrace.enable"), return 0); 247 248 int ret = CreateBootEventFile(BOOTEVENT_OUTPUT_PATH "bootup.trace", S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); 249 INIT_CHECK_RETURN_VALUE(ret == 0, -1); 250 FILE *tmpFile = fopen(BOOTEVENT_OUTPUT_PATH "bootup.trace", "wr"); 251 INIT_CHECK_RETURN_VALUE(tmpFile != NULL, -1); 252 cJSON *root = cJSON_CreateArray(); 253 INIT_CHECK(root != NULL, (void)fclose(tmpFile); 254 return -1); 255 256 OH_ListTraversal(&bootEventList, (void *)root, BootEventTraversal, 0); 257 char *buff = cJSON_Print(root); 258 if (buff == NULL) { 259 cJSON_Delete(root); 260 (void)fclose(tmpFile); 261 return -1; 262 } 263 INIT_CHECK_ONLY_ELOG(fprintf(tmpFile, "%s\n", buff) >= 0, "save boot event file failed"); 264 free(buff); 265 cJSON_Delete(root); 266 (void)fflush(tmpFile); 267 (void)fclose(tmpFile); 268 return 0; 269} 270 271static void ReportSysEvent(void) 272{ 273 INIT_CHECK(GetBootSwitchEnable("persist.init.bootevent.enable"), return); 274#ifndef STARTUP_INIT_TEST 275 InitModuleMgrInstall("eventmodule"); 276 InitModuleMgrUnInstall("eventmodule"); 277#endif 278 return; 279} 280 281static void BootCompleteClearAll(void) 282{ 283 InitGroupNode *node = GetNextGroupNode(NODE_TYPE_SERVICES, NULL); 284 while (node != NULL) { 285 if (node->data.service == NULL) { 286 node = GetNextGroupNode(NODE_TYPE_SERVICES, node); 287 continue; 288 } 289 for (int i = HOOK_ID_BOOTEVENT; i < HOOK_ID_BOOTEVENT_MAX; i++) { 290 ServiceExtData *extData = GetServiceExtData(node->name, i); 291 if (extData == NULL) { 292 return; 293 } 294 free(((BOOT_EVENT_PARAM_ITEM *)extData->data)->paramName); 295 OH_ListRemove(&((BOOT_EVENT_PARAM_ITEM *)extData->data)->node); 296 DelServiceExtData(node->name, i); 297 } 298 } 299 300 // clear init boot event 301 OH_ListRemoveAll(&bootEventList, BootEventDestroy); 302 g_bootEventNum = 0; 303} 304 305static void WriteBooteventSysParam(const char *paramName) 306{ 307 char buf[64]; 308 long long uptime; 309 char name[PARAM_NAME_LEN_MAX]; 310 311 uptime = GetUptimeInMicroSeconds(NULL); 312 313 INIT_CHECK_ONLY_ELOG(snprintf_s(buf, sizeof(buf), sizeof(buf) - 1, "%lld", uptime) >= 0, 314 "snprintf_s buf failed"); 315 INIT_CHECK_ONLY_ELOG(snprintf_s(name, sizeof(name), sizeof(name) - 1, "ohos.boot.time.%s", paramName) >= 0, 316 "snprintf_s name failed"); 317 SystemWriteParam(name, buf); 318} 319 320static int BootEventParaFireByName(const char *paramName) 321{ 322 BOOT_EVENT_PARAM_ITEM *found = NULL; 323 324 char *bootEventValue = strrchr(paramName, '.'); 325 INIT_CHECK(bootEventValue != NULL, return 0); 326 bootEventValue[0] = '\0'; 327 328 WriteBooteventSysParam(paramName); 329 330 found = (BOOT_EVENT_PARAM_ITEM *)OH_ListFind(&bootEventList, (void *)paramName, BootEventParaListCompareProc); 331 if (found == NULL) { 332 return 0; 333 } 334 335 // Already fired 336 if (found->timestamp[BOOTEVENT_READY].tv_sec > 0) { 337 return 0; 338 } 339 INIT_CHECK_RETURN_VALUE(clock_gettime(CLOCK_MONOTONIC, 340 &(found->timestamp[BOOTEVENT_READY])) == 0, 0); 341 342 g_bootEventNum--; 343 SetServiceBooteventHookMgr(NULL, paramName, 2); // 2: bootevent service has ready 344 // Check if all boot event params are fired 345 if (g_bootEventNum > 0) { 346 return 0; 347 } 348 // All parameters are fired, set boot completed now ... 349 INIT_LOGI("All boot events are fired, boot complete now ..."); 350 SystemWriteParam(BOOT_EVENT_BOOT_COMPLETED, "true"); 351 g_isBootCompleted = true; 352 SaveServiceBootEvent(); 353 // report complete event 354 ReportSysEvent(); 355 BootCompleteClearAll(); 356#ifndef STARTUP_INIT_TEST 357 HookMgrExecute(GetBootStageHookMgr(), INIT_BOOT_COMPLETE, NULL, NULL); 358#endif 359 RemoveCmdExecutor("bootevent", -1); 360 return 1; 361} 362 363#define BOOT_EVENT_FIELD_NAME "bootevents" 364static void ServiceParseBootEventHook(SERVICE_PARSE_CTX *serviceParseCtx) 365{ 366 int cnt; 367 cJSON *bootEvents = cJSON_GetObjectItem(serviceParseCtx->serviceNode, BOOT_EVENT_FIELD_NAME); 368 369 // No boot events in config file 370 if (bootEvents == NULL) { 371 return; 372 } 373 SERVICE_INFO_CTX ctx = {0}; 374 ctx.serviceName = serviceParseCtx->serviceName; 375 HookMgrExecute(GetBootStageHookMgr(), INIT_SERVICE_CLEAR, (void *)&ctx, NULL); 376 // Single boot event in config file 377 if (!cJSON_IsArray(bootEvents)) { 378 if (AddServiceBootEvent(serviceParseCtx->serviceName, 379 cJSON_GetStringValue(bootEvents)) != 0) { 380 INIT_LOGI("Add service bootEvent failed %s", serviceParseCtx->serviceName); 381 return; 382 } 383 return; 384 } 385 386 // Multiple boot events in config file 387 cnt = cJSON_GetArraySize(bootEvents); 388 for (int i = 0; i < cnt; i++) { 389 cJSON *item = cJSON_GetArrayItem(bootEvents, i); 390 if (AddServiceBootEvent(serviceParseCtx->serviceName, 391 cJSON_GetStringValue(item)) != 0) { 392 INIT_LOGI("Add service bootEvent failed %s", serviceParseCtx->serviceName); 393 continue; 394 } 395 } 396} 397 398static int g_finished = 0; 399static int DoBootEventCmd(int id, const char *name, int argc, const char **argv) 400{ 401 if (g_finished) { 402 return 0; 403 } 404 405 PLUGIN_CHECK(argc >= 1, return -1, "Invalid parameter"); 406 if (strcmp(argv[0], "init") == 0) { 407 if (argc < 2) { // 2 args 408 return 0; 409 } 410 AddInitBootEvent(argv[1]); 411 } else { 412 // argv[0] samgr.ready.true 413 g_finished = BootEventParaFireByName(argv[0]); 414 } 415 return 0; 416} 417 418static void AddReservedBooteventsByFile(const char *name) 419{ 420 char buf[MAX_PATH_LEN]; 421 422 FILE *file = fopen(name, "r"); 423 if (file == NULL) { 424 return; 425 } 426 427 while (fgets((void *)buf, sizeof(buf) - 1, file)) { 428 buf[sizeof(buf) - 1] = '\0'; 429 char *end = strchr(buf, '\r'); 430 if (end != NULL) { 431 *end = '\0'; 432 } 433 end = strchr(buf, '\n'); 434 if (end != NULL) { 435 *end = '\0'; 436 } 437 INIT_LOGI("Got priv-app bootevent: %s", buf); 438 AddBootEventItemByName(buf); 439 } 440 (void)fclose(file); 441} 442 443static void AddReservedBootevents(void) 444{ 445 CfgFiles *files = GetCfgFiles("etc/init/priv_app.bootevents"); 446 for (int i = MAX_CFG_POLICY_DIRS_CNT - 1; files && i >= 0; i--) { 447 if (files->paths[i]) { 448 AddReservedBooteventsByFile(files->paths[i]); 449 } 450 } 451 FreeCfgFiles(files); 452} 453 454static int DoUnsetBootEventCmd(int id, const char *name, int argc, const char **argv) 455{ 456 if ((argc < 1) || (argv[0] == NULL) || (strlen(argv[0]) <= strlen(BOOT_EVENT_PARA_PREFIX)) || 457 (strncmp(argv[0], BOOT_EVENT_PARA_PREFIX, strlen(BOOT_EVENT_PARA_PREFIX)) != 0)) { 458 return INIT_EPARAMETER; 459 } 460 const char *eventName = argv[0] + strlen(BOOT_EVENT_PARA_PREFIX); 461 BOOT_EVENT_PARAM_ITEM *item = 462 (BOOT_EVENT_PARAM_ITEM *)OH_ListFind(&bootEventList, (void *)eventName, BootEventParaListCompareProc); 463 PLUGIN_CHECK(item != NULL, return INIT_EPARAMETER, "item NULL"); 464 465 SystemWriteParam(argv[0], "false"); 466 if (g_finished != 0) { 467 SystemWriteParam(BOOT_EVENT_BOOT_COMPLETED, "false"); 468 g_isBootCompleted = false; 469 g_finished = 0; 470 } 471 472 item->timestamp[BOOTEVENT_READY].tv_sec = 0; 473 g_bootEventNum++; 474 INIT_LOGI("UnsetBootEvent %s g_bootEventNum:%d", argv[0], g_bootEventNum); 475 return INIT_OK; 476} 477 478static int ParamSetBootEventHook(const HOOK_INFO *hookInfo, void *cookie) 479{ 480 AddReservedBootevents(); 481 AddCmdExecutor("bootevent", DoBootEventCmd); 482 AddCmdExecutor("unset_bootevent", DoUnsetBootEventCmd); 483 return 0; 484} 485 486static void SetServiceBootEventFork(SERVICE_INFO_CTX *serviceCtx) 487{ 488 BOOT_EVENT_PARAM_ITEM *item; 489 for (int i = HOOK_ID_BOOTEVENT; i < HOOK_ID_BOOTEVENT_MAX; i++) { 490 ServiceExtData *extData = GetServiceExtData(serviceCtx->serviceName, i); 491 if (extData == NULL) { 492 return; 493 } 494 item = (BOOT_EVENT_PARAM_ITEM *)extData->data; 495 if (serviceCtx->reserved != NULL) { 496 item->pid = *((int *)serviceCtx->reserved); 497 } 498 INIT_CHECK_ONLY_RETURN(clock_gettime(CLOCK_MONOTONIC, 499 &(item->timestamp[BOOTEVENT_FORK])) == 0); 500 } 501} 502 503ListNode *GetBootEventList(void) 504{ 505 return &bootEventList; 506} 507 508bool IsBootCompleted(void) 509{ 510 return g_isBootCompleted; 511} 512 513static void AddCmdBootEvent(INIT_CMD_INFO *cmdCtx) 514{ 515 INIT_TIMING_STAT *timeStat = (INIT_TIMING_STAT *)cmdCtx->reserved; 516 long long diff = InitDiffTime(timeStat); 517 // If not time cost, just ignore 518 if (diff < SAVEINITBOOTEVENTMSEC) { 519 return; 520 } 521 BOOT_EVENT_PARAM_ITEM *item = calloc(1, sizeof(BOOT_EVENT_PARAM_ITEM)); 522 if (item == NULL) { 523 return; 524 } 525 OH_ListInit(&item->node); 526 item->timestamp[BOOTEVENT_FORK] = timeStat->startTime; 527 item->timestamp[BOOTEVENT_READY] = timeStat->endTime; 528 int cmdLen = strlen(cmdCtx->cmdName) + strlen(cmdCtx->cmdContent) + 1; // 2 args 1 '\0' 529 item->paramName = calloc(1, cmdLen); 530 if (item->paramName == NULL) { 531 free(item); 532 return; 533 } 534 INIT_CHECK_ONLY_ELOG(snprintf_s(item->paramName, cmdLen, cmdLen - 1, "%s%s", 535 cmdCtx->cmdName, cmdCtx->cmdContent) >= 0, 536 "combine cmd args failed"); 537 item->flags = BOOTEVENT_TYPE_CMD; 538 OH_ListAddTail(&bootEventList, (ListNode *)&item->node); 539} 540 541static int RecordInitCmd(const HOOK_INFO *info, void *cookie) 542{ 543 if (cookie == NULL) { 544 return 0; 545 } 546 AddCmdBootEvent((INIT_CMD_INFO *)cookie); 547 return 0; 548} 549 550MODULE_CONSTRUCTOR(void) 551{ 552 // Add hook to record time-cost commands 553 HOOK_INFO info = {INIT_CMD_RECORD, 0, RecordInitCmd, NULL}; 554 HookMgrAddEx(GetBootStageHookMgr(), &info); 555 556 // Add hook to parse all services with bootevents 557 InitAddServiceParseHook(ServiceParseBootEventHook); 558 559 // Add hook to record start time for services with bootevents 560 InitAddServiceHook(SetServiceBootEventFork, INIT_SERVICE_FORK_AFTER); 561 562 InitAddGlobalInitHook(0, ParamSetBootEventHook); 563} 564