1/*
2 * Copyright (c) 2021 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 <ctype.h>
16
17#include "init_param.h"
18#include "init_service_manager.h"
19#include "init_utils.h"
20#include "param_manager.h"
21#include "param_message.h"
22#include "param_utils.h"
23#include "trigger_checker.h"
24#include "trigger_manager.h"
25#include "securec.h"
26#include "hookmgr.h"
27#include "bootstage.h"
28
29#define MAX_TRIGGER_COUNT_RUN_ONCE 20
30#define MAX_TRIGGER_NAME_LENGTH 256
31static TriggerWorkSpace g_triggerWorkSpace = {};
32
33static int DoTriggerExecute_(const TriggerNode *trigger, const char *content, uint32_t size)
34{
35    PARAM_CHECK(trigger != NULL, return -1, "Invalid trigger");
36    PARAM_LOGV("Do execute trigger %s type: %d", GetTriggerName(trigger), trigger->type);
37    PARAM_CHECK(trigger->type <= TRIGGER_UNKNOW, return -1, "Invalid trigger type %d", trigger->type);
38    CommandNode *cmd = GetNextCmdNode((JobNode *)trigger, NULL);
39    while (cmd != NULL) {
40#ifndef STARTUP_INIT_TEST
41        DoCmdByIndex(cmd->cmdKeyIndex, cmd->content, &cmd->cfgContext);
42#endif
43        cmd = GetNextCmdNode((JobNode *)trigger, cmd);
44    }
45    return 0;
46}
47
48static int DoTriggerCheckResult(TriggerNode *trigger, const char *content, uint32_t size)
49{
50    UNUSED(content);
51    UNUSED(size);
52    if (TRIGGER_IN_QUEUE(trigger)) {
53        PARAM_LOGI("DoTiggerExecute trigger %s has been waiting execute", GetTriggerName(trigger));
54        return 0;
55    }
56    TRIGGER_SET_FLAG(trigger, TRIGGER_FLAGS_QUEUE);
57    PARAM_LOGV("Add trigger %s to execute queue", GetTriggerName(trigger));
58    ExecuteQueuePush(&g_triggerWorkSpace, trigger);
59    return 0;
60}
61
62static int ExecuteTriggerImmediately(TriggerNode *trigger, const char *content, uint32_t size)
63{
64    PARAM_CHECK(trigger != NULL, return -1, "Invalid trigger");
65    PARAM_LOGV("ExecuteTriggerImmediately trigger %s", GetTriggerName(trigger));
66    TriggerHeader *triggerHead = GetTriggerHeader(&g_triggerWorkSpace, trigger->type);
67    if (triggerHead != NULL) {
68        triggerHead->executeTrigger(trigger, content, size);
69        TRIGGER_CLEAR_FLAG(trigger, TRIGGER_FLAGS_QUEUE);
70
71        if (TRIGGER_TEST_FLAG(trigger, TRIGGER_FLAGS_ONCE)) {
72            FreeTrigger(&g_triggerWorkSpace, trigger);
73        }
74    }
75    return 0;
76}
77
78static void StartTriggerExecute_(TriggerNode *trigger, const char *content, uint32_t size)
79{
80    TriggerHeader *triggerHead = GetTriggerHeader(&g_triggerWorkSpace, trigger->type);
81    if (triggerHead != NULL) {
82        PARAM_LOGV("StartTriggerExecute_ trigger %s flags:0x%04x",
83            GetTriggerName(trigger), trigger->flags);
84        triggerHead->executeTrigger(trigger, content, size);
85        TRIGGER_CLEAR_FLAG(trigger, TRIGGER_FLAGS_QUEUE);
86        if (TRIGGER_TEST_FLAG(trigger, TRIGGER_FLAGS_SUBTRIGGER)) { // boot && xxx=xxx trigger
87            const char *condition = triggerHead->getCondition(trigger);
88            CheckTrigger(&g_triggerWorkSpace, TRIGGER_UNKNOW, condition, strlen(condition), ExecuteTriggerImmediately);
89        }
90        if (TRIGGER_TEST_FLAG(trigger, TRIGGER_FLAGS_ONCE)) {
91            FreeTrigger(&g_triggerWorkSpace, trigger);
92        }
93    }
94}
95
96static void ExecuteQueueWork(uint32_t maxCount, void (*bootStateChange)(int start, const char *))
97{
98    uint32_t executeCount = 0;
99    TriggerNode *trigger = ExecuteQueuePop(&g_triggerWorkSpace);
100    char triggerName[MAX_TRIGGER_NAME_LENGTH] = {0};
101    while (trigger != NULL) {
102        int ret = strcpy_s(triggerName, sizeof(triggerName), GetTriggerName(trigger));
103        PARAM_CHECK(ret == 0, return, "strcpy triggerName failed!");
104        if (bootStateChange != NULL) {
105            bootStateChange(0, triggerName);
106        }
107
108        StartTriggerExecute_(trigger, NULL, 0);
109        if (bootStateChange != NULL) {
110            bootStateChange(1, triggerName);
111        }
112        executeCount++;
113        if (executeCount > maxCount) {
114            break;
115        }
116        trigger = ExecuteQueuePop(&g_triggerWorkSpace);
117    }
118}
119
120PARAM_STATIC void ProcessBeforeEvent(const ParamTaskPtr stream,
121    uint64_t eventId, const uint8_t *content, uint32_t size)
122{
123    PARAM_LOGV("ProcessBeforeEvent %s ", (char *)content);
124    switch (eventId) {
125        case EVENT_TRIGGER_PARAM: {
126            CheckTrigger(&g_triggerWorkSpace, TRIGGER_PARAM,
127                (const char *)content, size, DoTriggerCheckResult);
128            ExecuteQueueWork(MAX_TRIGGER_COUNT_RUN_ONCE, NULL);
129            break;
130        }
131        case EVENT_TRIGGER_BOOT: {
132            if (g_triggerWorkSpace.bootStateChange != NULL) {
133                g_triggerWorkSpace.bootStateChange(0, (const char *)content);
134            }
135            CheckTrigger(&g_triggerWorkSpace, TRIGGER_BOOT,
136                (const char *)content, size, DoTriggerCheckResult);
137            ExecuteQueueWork(1, NULL);
138            if (g_triggerWorkSpace.bootStateChange != NULL) {
139                g_triggerWorkSpace.bootStateChange(1, (const char *)content);
140            }
141            ExecuteQueueWork(MAX_TRIGGER_COUNT_RUN_ONCE, g_triggerWorkSpace.bootStateChange);
142            break;
143        }
144        case EVENT_TRIGGER_PARAM_WAIT: {
145            CheckTrigger(&g_triggerWorkSpace, TRIGGER_PARAM_WAIT,
146                (const char *)content, size, ExecuteTriggerImmediately);
147            break;
148        }
149        case EVENT_TRIGGER_PARAM_WATCH: {
150            CheckTrigger(&g_triggerWorkSpace, TRIGGER_PARAM_WATCH,
151                (const char *)content, size, ExecuteTriggerImmediately);
152            break;
153        }
154        default:
155            break;
156    }
157}
158
159static void SendTriggerEvent(int type, const char *content, uint32_t contentLen)
160{
161    PARAM_CHECK(content != NULL, return, "Invalid param");
162    PARAM_LOGV("SendTriggerEvent type %d content %s", type, content);
163    ParamEventSend(g_triggerWorkSpace.eventHandle, (uint64_t)type, content, contentLen);
164}
165
166void PostParamTrigger(int type, const char *name, const char *value)
167{
168    PARAM_CHECK(name != NULL && value != NULL, return, "Invalid param");
169    uint32_t bufferSize = strlen(name) + strlen(value) + 1 + 1 + 1;
170    PARAM_CHECK(bufferSize < (PARAM_CONST_VALUE_LEN_MAX + PARAM_NAME_LEN_MAX + 1 + 1 + 1),
171        return, "bufferSize is longest %d", bufferSize);
172    char *buffer = (char *)calloc(1, bufferSize);
173    PARAM_CHECK(buffer != NULL, return, "Failed to alloc memory for  param %s", name);
174    int ret = sprintf_s(buffer, bufferSize - 1, "%s=%s", name, value);
175    PARAM_CHECK(ret > EOK, free(buffer);
176        return, "Failed to copy param");
177    SendTriggerEvent(type, buffer, strlen(buffer));
178    free(buffer);
179}
180
181void PostTrigger(EventType type, const char *content, uint32_t contentLen)
182{
183    PARAM_CHECK(content != NULL && contentLen > 0, return, "Invalid param");
184    SendTriggerEvent(type, content, contentLen);
185}
186
187static int GetTriggerType(const char *type)
188{
189    if (strncmp("param:", type, strlen("param:")) == 0) {
190        return TRIGGER_PARAM;
191    }
192    if (strncmp("boot-service:", type, strlen("boot-service:")) == 0) {
193        return TRIGGER_BOOT;
194    }
195    const char *triggerTypeStr[] = {
196        "pre-init", "boot", "early-init", "init", "early-init", "late-init", "post-init",
197        "fs", "early-fs", "post-fs", "late-fs", "early-boot", "post-fs-data", "reboot", "suspend"
198    };
199    for (size_t i = 0; i < ARRAY_LENGTH(triggerTypeStr); i++) {
200        if (strcmp(triggerTypeStr[i], type) == 0) {
201            return TRIGGER_BOOT;
202        }
203    }
204    return TRIGGER_UNKNOW;
205}
206
207static int GetCommandInfo(const char *cmdLine, int *cmdKeyIndex, char **content)
208{
209    const char *matchCmd = GetMatchCmd(cmdLine, cmdKeyIndex);
210    PARAM_CHECK(matchCmd != NULL, return -1, "Command not support %s", cmdLine);
211    char *str = strstr(cmdLine, matchCmd);
212    if (str != NULL) {
213        str += strlen(matchCmd);
214    }
215    while (str != NULL && isspace(*str)) {
216        str++;
217    }
218    *content = str;
219    return 0;
220}
221
222static void ParseJobHookExecute(const char *name, const cJSON *jobNode)
223{
224    JOB_PARSE_CTX context;
225
226    context.jobName = name;
227    context.jobNode = jobNode;
228
229    (void)HookMgrExecute(GetBootStageHookMgr(), INIT_JOB_PARSE, (void *)(&context), NULL);
230}
231
232static int ParseTrigger_(const TriggerWorkSpace *workSpace,
233    const cJSON *triggerItem, int (*checkJobValid)(const char *jobName), const ConfigContext *cfgContext)
234{
235    PARAM_CHECK(triggerItem != NULL, return -1, "Invalid file");
236    PARAM_CHECK(workSpace != NULL, return -1, "Failed to create trigger list");
237    char *name = cJSON_GetStringValue(cJSON_GetObjectItem(triggerItem, "name"));
238    PARAM_CHECK(name != NULL, return -1, "Can not get name from cfg");
239    char *condition = cJSON_GetStringValue(cJSON_GetObjectItem(triggerItem, "condition"));
240    int type = GetTriggerType(name);
241    PARAM_CHECK(type <= TRIGGER_UNKNOW, return -1, "Failed to get trigger index");
242    if (type != TRIGGER_BOOT && checkJobValid != NULL && checkJobValid(name) != 0) {
243        PARAM_LOGI("Trigger %s not exist in group", name);
244        return 0;
245    }
246
247    TriggerHeader *header = GetTriggerHeader(workSpace, type);
248    PARAM_CHECK(header != NULL, return -1, "Failed to get header %d", type);
249    JobNode *trigger = UpdateJobTrigger(workSpace, type, condition, name);
250    PARAM_CHECK(trigger != NULL, return -1, "Failed to create trigger %s", name);
251    PARAM_LOGV("ParseTrigger %s type %d count %d", name, type, header->triggerCount);
252    cJSON *cmdItems = cJSON_GetObjectItem(triggerItem, CMDS_ARR_NAME_IN_JSON);
253    if (cmdItems == NULL || !cJSON_IsArray(cmdItems)) {
254        return 0;
255    }
256    int cmdLinesCnt = cJSON_GetArraySize(cmdItems);
257    PARAM_CHECK(cmdLinesCnt > 0, return -1, "Command array size must positive %s", name);
258
259    int ret;
260    int cmdKeyIndex = 0;
261    for (int i = 0; (i < cmdLinesCnt) && (i < TRIGGER_MAX_CMD); ++i) {
262        char *cmdLineStr = cJSON_GetStringValue(cJSON_GetArrayItem(cmdItems, i));
263        PARAM_CHECK(cmdLineStr != NULL, continue, "Command is null");
264
265        char *content = NULL;
266        ret = GetCommandInfo(cmdLineStr, &cmdKeyIndex, &content);
267        PARAM_CHECK(ret == 0, continue, "Command not support %s", cmdLineStr);
268        ret = AddCommand(trigger, (uint32_t)cmdKeyIndex, content, cfgContext);
269        PARAM_CHECK(ret == 0, continue, "Failed to add command %s", cmdLineStr);
270        header->cmdNodeCount++;
271    }
272    return 0;
273}
274
275int ParseTriggerConfig(const cJSON *fileRoot, int (*checkJobValid)(const char *jobName), void *context)
276{
277    PARAM_CHECK(g_triggerWorkSpace.eventHandle != NULL, return -1, "Invalid trigger data");
278    PARAM_CHECK(fileRoot != NULL, return -1, "Invalid file");
279    ConfigContext *cfgContext = (ConfigContext *)context;
280    cJSON *triggers = cJSON_GetObjectItemCaseSensitive(fileRoot, TRIGGER_ARR_NAME_IN_JSON);
281    if (triggers == NULL || !cJSON_IsArray(triggers)) {
282        return 0;
283    }
284    int size = cJSON_GetArraySize(triggers);
285    PARAM_CHECK(size > 0, return -1, "Trigger array size must positive");
286
287    for (int i = 0; i < size && i < TRIGGER_MAX_CMD; ++i) {
288        cJSON *item = cJSON_GetArrayItem(triggers, i);
289        ParseTrigger_(&g_triggerWorkSpace, item, checkJobValid, cfgContext);
290        /*
291         * execute job parsing hooks
292         */
293        ParseJobHookExecute(cJSON_GetStringValue(cJSON_GetObjectItem(item, "name")), item);
294    }
295    return 0;
296}
297
298int CheckAndMarkTrigger(int type, const char *name)
299{
300    TriggerHeader *triggerHead = GetTriggerHeader(&g_triggerWorkSpace, type);
301    if (triggerHead) {
302        return triggerHead->checkAndMarkTrigger(&g_triggerWorkSpace, type, name);
303    }
304    return 0;
305}
306
307int InitTriggerWorkSpace(void)
308{
309    if (g_triggerWorkSpace.eventHandle != NULL) {
310        return 0;
311    }
312    g_triggerWorkSpace.bootStateChange = NULL;
313    ParamEventTaskCreate(&g_triggerWorkSpace.eventHandle, ProcessBeforeEvent);
314    PARAM_CHECK(g_triggerWorkSpace.eventHandle != NULL, return -1, "Failed to event handle");
315
316    // executeQueue
317    g_triggerWorkSpace.executeQueue.executeQueue = calloc(1, TRIGGER_EXECUTE_QUEUE * sizeof(TriggerNode *));
318    PARAM_CHECK(g_triggerWorkSpace.executeQueue.executeQueue != NULL,
319        return -1, "Failed to alloc memory for executeQueue");
320    g_triggerWorkSpace.executeQueue.queueCount = TRIGGER_EXECUTE_QUEUE;
321    g_triggerWorkSpace.executeQueue.startIndex = 0;
322    g_triggerWorkSpace.executeQueue.endIndex = 0;
323    InitTriggerHead(&g_triggerWorkSpace);
324    RegisterTriggerExec(TRIGGER_BOOT, DoTriggerExecute_);
325    RegisterTriggerExec(TRIGGER_PARAM, DoTriggerExecute_);
326    RegisterTriggerExec(TRIGGER_UNKNOW, DoTriggerExecute_);
327    PARAM_LOGV("InitTriggerWorkSpace success");
328    return 0;
329}
330
331void CloseTriggerWorkSpace(void)
332{
333    for (size_t i = 0; i < sizeof(g_triggerWorkSpace.triggerHead) / sizeof(g_triggerWorkSpace.triggerHead[0]); i++) {
334        ClearTrigger(&g_triggerWorkSpace, i);
335    }
336    OH_HashMapDestory(g_triggerWorkSpace.hashMap, NULL);
337    g_triggerWorkSpace.hashMap = NULL;
338    free(g_triggerWorkSpace.executeQueue.executeQueue);
339    g_triggerWorkSpace.executeQueue.executeQueue = NULL;
340    ParamTaskClose(g_triggerWorkSpace.eventHandle);
341    g_triggerWorkSpace.eventHandle = NULL;
342}
343
344TriggerWorkSpace *GetTriggerWorkSpace(void)
345{
346    return &g_triggerWorkSpace;
347}
348
349void RegisterTriggerExec(int type,
350    int32_t (*executeTrigger)(const struct tagTriggerNode_ *, const char *, uint32_t))
351{
352    TriggerHeader *triggerHead = GetTriggerHeader(&g_triggerWorkSpace, type);
353    if (triggerHead != NULL) {
354        triggerHead->executeTrigger = executeTrigger;
355    }
356}
357
358void DoTriggerExec(const char *triggerName)
359{
360    PARAM_CHECK(g_triggerWorkSpace.eventHandle != NULL, return, "Invalid trigger data");
361    PARAM_CHECK(triggerName != NULL, return, "Invalid param");
362    JobNode *trigger = GetTriggerByName(&g_triggerWorkSpace, triggerName);
363    if (trigger != NULL && !TRIGGER_IN_QUEUE((TriggerNode *)trigger)) {
364        PARAM_LOGV("Trigger job %s", trigger->name);
365        TRIGGER_SET_FLAG((TriggerNode *)trigger, TRIGGER_FLAGS_QUEUE);
366        ExecuteQueuePush(&g_triggerWorkSpace, (TriggerNode *)trigger);
367    } else {
368        PARAM_LOGW("Can not find trigger %s", triggerName);
369    }
370}
371
372void DoJobExecNow(const char *triggerName)
373{
374    PARAM_CHECK(g_triggerWorkSpace.eventHandle != NULL, return, "Invalid trigger data");
375    PARAM_CHECK(triggerName != NULL, return, "Invalid param");
376    JobNode *trigger = GetTriggerByName(&g_triggerWorkSpace, triggerName);
377    if (trigger != NULL) {
378        if (strncmp(triggerName, "reboot", strlen("reboot")) == 0) {
379            HookMgrExecute(GetBootStageHookMgr(), INIT_SHUT_DETECTOR, NULL, NULL);
380        }
381        StartTriggerExecute_((TriggerNode *)trigger, NULL, 0);
382    }
383}
384
385int AddCompleteJob(const char *name, const char *condition, const char *cmdContent)
386{
387    PARAM_CHECK(g_triggerWorkSpace.eventHandle != NULL, return -1, "Invalid trigger data");
388    PARAM_CHECK(name != NULL, return -1, "Invalid name");
389    PARAM_CHECK(cmdContent != NULL, return -1, "Invalid cmdContent");
390    int type = GetTriggerType(name);
391    PARAM_CHECK(type <= TRIGGER_UNKNOW, return -1, "Failed to get trigger index");
392    TriggerHeader *header = GetTriggerHeader(&g_triggerWorkSpace, type);
393    PARAM_CHECK(header != NULL, return -1, "Failed to get header %d", type);
394
395    JobNode *trigger = UpdateJobTrigger(&g_triggerWorkSpace, type, condition, name);
396    PARAM_CHECK(trigger != NULL, return -1, "Failed to create trigger");
397    char *content = NULL;
398    int cmdKeyIndex = 0;
399    int ret = GetCommandInfo(cmdContent, &cmdKeyIndex, &content);
400    PARAM_CHECK(ret == 0, return -1, "Command not support %s", cmdContent);
401    ret = AddCommand(trigger, (uint32_t)cmdKeyIndex, content, NULL); // use default context
402    PARAM_CHECK(ret == 0, return -1, "Failed to add command %s", cmdContent);
403    header->cmdNodeCount++;
404    PARAM_LOGV("AddCompleteJob %s type %d count %d", name, type, header->triggerCount);
405    return 0;
406}
407
408void RegisterBootStateChange(void (*bootStateChange)(int, const char *))
409{
410    if (bootStateChange != NULL) {
411        g_triggerWorkSpace.bootStateChange = bootStateChange;
412    }
413}
414