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