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