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