551 lines
14 KiB
C
551 lines
14 KiB
C
/****************************************************************************
|
|
* apps/system/init/action.c
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership. The
|
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <sys/param.h>
|
|
#include <unistd.h>
|
|
#include <fnmatch.h>
|
|
#include <nuttx/clock.h>
|
|
|
|
#include "action.h"
|
|
#include "builtin.h"
|
|
#include "init.h"
|
|
#include "property.h"
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
#define ACTION_SECTION_PROP_PREFIX "property:"
|
|
#define ACTION_SECTION_AND_PREFIX "&&"
|
|
|
|
#define ACTION_PROP_KEY_DEFAULT "default"
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
struct event_arg_s
|
|
{
|
|
FAR const char *key;
|
|
FAR const char *value;
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_SYSTEM_INIT_DEBUG
|
|
static void init_dump_action(FAR struct action_s *action);
|
|
#else
|
|
# define init_dump_action(a)
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_SYSTEM_INIT_DEBUG
|
|
static void init_dump_action(FAR struct action_s *action)
|
|
{
|
|
FAR struct action_cmd_s *cmd;
|
|
size_t i;
|
|
|
|
init_debug("action %p", action);
|
|
for (i = 0; i < nitems(action->events); i++)
|
|
{
|
|
if (!action->events[i].key)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
init_debug(" %s%s%s", action->events[i].key,
|
|
action->events[i].invert ? "!=" : "==",
|
|
action->events[i].value);
|
|
}
|
|
|
|
list_for_every_entry(&action->cmds, cmd, struct action_cmd_s, node)
|
|
{
|
|
init_dump_args(cmd->argc, cmd->argv);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void add_ready(FAR struct action_manager_s *am,
|
|
FAR struct action_s *a)
|
|
{
|
|
FAR struct action_s *ready;
|
|
#ifdef CONFIG_SYSTEM_INIT_DEBUG
|
|
FAR struct action_cmd_s *cmd;
|
|
|
|
list_for_every_entry(&a->cmds, cmd, struct action_cmd_s, node)
|
|
{
|
|
init_debug("add ready %p %s", cmd, cmd->argv[0]);
|
|
}
|
|
#endif
|
|
|
|
list_for_every_entry(&am->ready_actions, ready, struct action_s,
|
|
ready_node)
|
|
{
|
|
if (ready == a)
|
|
{
|
|
init_debug("event %p(%s:%s) already on the queue", a,
|
|
a->events[0].key, a->events[0].value);
|
|
init_dump_action(a);
|
|
return;
|
|
}
|
|
}
|
|
|
|
list_add_tail(&am->ready_actions, &a->ready_node);
|
|
}
|
|
|
|
static int parse_event(FAR char *buf, FAR struct action_event_s *events,
|
|
size_t count)
|
|
{
|
|
FAR char *value;
|
|
FAR char *key;
|
|
size_t i;
|
|
|
|
value = strchr(buf, '=');
|
|
if (!value || !*(value + 1))
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
*(value++) = '\0';
|
|
key = strchr(buf, ':');
|
|
if (!key || !*(key + 1))
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
*(key++) = '\0';
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
if (events[i].key)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (key[strlen(key) - 1] == '!')
|
|
{
|
|
events[i].invert = true;
|
|
key[strlen(key) - 1] = '\0'; /* Skip '!' */
|
|
}
|
|
else
|
|
{
|
|
events[i].invert = false;
|
|
}
|
|
|
|
events[i].key = strdup(key);
|
|
events[i].value = strdup(value);
|
|
events[i].pending = false;
|
|
|
|
if (events[i].key && events[i].value)
|
|
{
|
|
init_debug("added action event key:%s value:%s", events[i].key,
|
|
events[i].value);
|
|
return 0;
|
|
}
|
|
|
|
for (; i >= 0; i--)
|
|
{
|
|
if (events[i].key)
|
|
{
|
|
free((FAR void *)events[i].key);
|
|
events[i].key = NULL;
|
|
}
|
|
|
|
if (events[i].value)
|
|
{
|
|
free(events[i].value);
|
|
}
|
|
}
|
|
|
|
return -errno;
|
|
}
|
|
|
|
init_err("dropped action event key:%s value:%s", key, value);
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
static int event_callback(FAR struct action_manager_s *am,
|
|
FAR struct action_s *a,
|
|
FAR struct action_event_s *event,
|
|
FAR void *argument)
|
|
{
|
|
struct event_arg_s *arg = argument;
|
|
|
|
if (!strcmp(arg->key, event->key))
|
|
{
|
|
if (event->invert != fnmatch(event->value, arg->value, 0))
|
|
{
|
|
event->pending = false;
|
|
}
|
|
else if (!event->pending)
|
|
{
|
|
event->pending = true;
|
|
init_debug("trigger %s%s%s", event->key,
|
|
event->invert ? "!=" : "==",
|
|
event->value);
|
|
}
|
|
}
|
|
|
|
return event->pending;
|
|
}
|
|
|
|
static FAR char *parse_operator(FAR char *pos,
|
|
FAR enum action_cmd_op_e *op)
|
|
{
|
|
bool in_quotes = false;
|
|
|
|
for (; *pos != '\0'; pos++)
|
|
{
|
|
if (*pos == '"')
|
|
{
|
|
in_quotes = !in_quotes;
|
|
continue;
|
|
}
|
|
|
|
if (in_quotes)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (*pos == '&' && *(pos + 1) == '&')
|
|
{
|
|
*op = CMD_OP_AND;
|
|
*pos = '\0';
|
|
return pos + 2;
|
|
}
|
|
|
|
if (*pos == '|' && *(pos + 1) == '|')
|
|
{
|
|
*op = CMD_OP_OR;
|
|
*pos = '\0';
|
|
return pos + 2;
|
|
}
|
|
}
|
|
|
|
*op = CMD_OP_NONE;
|
|
return pos;
|
|
}
|
|
|
|
static int parse_command(FAR char *pos,
|
|
FAR struct list_node *cmds)
|
|
{
|
|
FAR enum action_cmd_op_e op = CMD_OP_NONE;
|
|
FAR enum action_cmd_op_e next_op;
|
|
FAR struct action_cmd_s *cmd;
|
|
FAR char *next_pos;
|
|
|
|
while (*pos)
|
|
{
|
|
next_pos = parse_operator(pos, &next_op);
|
|
|
|
cmd = calloc(1, sizeof(*cmd));
|
|
if (cmd == NULL)
|
|
{
|
|
return -errno;
|
|
}
|
|
|
|
cmd->argc = init_parse_arguments(pos, true,
|
|
nitems(cmd->argv) - 1,
|
|
cmd->argv);
|
|
if (cmd->argc < 1)
|
|
{
|
|
free(cmd);
|
|
init_err("invalid argument");
|
|
return -EINVAL;
|
|
}
|
|
|
|
cmd->op = op;
|
|
list_add_tail(cmds, &cmd->node);
|
|
init_debug("add command %p(%s) to list %p", cmd, cmd->argv[0], cmds);
|
|
init_dump_args(cmd->argc, cmd->argv);
|
|
|
|
op = next_op;
|
|
pos = next_pos;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
int init_action_foreach_event(FAR struct action_manager_s *am,
|
|
init_action_event_cb cb,
|
|
FAR void *arg)
|
|
{
|
|
FAR struct action_s *a;
|
|
size_t i;
|
|
size_t m;
|
|
int ret = 0;
|
|
|
|
list_for_every_entry(&am->actions, a, struct action_s, node)
|
|
{
|
|
for (i = 0, m = 0; i < nitems(a->events) && a->events[i].key; i++)
|
|
{
|
|
ret = cb(am, a, &a->events[i], arg);
|
|
if (ret < 0)
|
|
{
|
|
break;
|
|
}
|
|
else if (ret > 0)
|
|
{
|
|
m++;
|
|
}
|
|
}
|
|
|
|
if (i > 0 && i == m)
|
|
{
|
|
add_ready(am, a);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void init_action_trigger_event(FAR struct action_manager_s *am,
|
|
FAR const char *key,
|
|
FAR const char *value)
|
|
{
|
|
struct event_arg_s arg;
|
|
|
|
arg.key = key;
|
|
arg.value = value;
|
|
init_action_foreach_event(am, event_callback, &arg);
|
|
}
|
|
|
|
int init_action_add_event(FAR struct action_manager_s *am,
|
|
FAR const char *event)
|
|
{
|
|
return init_property_set(am->prop, ACTION_PROP_KEY_DEFAULT, event);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: init_action_run_command
|
|
*
|
|
* Description:
|
|
* Execute the ready commands in the action.
|
|
*
|
|
* Input Parameters:
|
|
* am - Instance of Action Manager
|
|
*
|
|
* Returned Value:
|
|
* The expected next call interval (in milliseconds), where INT_MAX
|
|
* indicates blocking.
|
|
****************************************************************************/
|
|
|
|
int init_action_run_command(FAR struct action_manager_s *am)
|
|
{
|
|
FAR struct action_s *ready;
|
|
int ret;
|
|
|
|
if (am->pid_running != -1)
|
|
{
|
|
init_debug("waiting '%s' pid %d", am->running->argv[0],
|
|
am->pid_running);
|
|
return INT_MAX;
|
|
}
|
|
|
|
if (list_is_empty(&am->ready_actions))
|
|
{
|
|
return INT_MAX;
|
|
}
|
|
|
|
ready = list_peek_head_type(&am->ready_actions, struct action_s,
|
|
ready_node);
|
|
|
|
if (!am->running)
|
|
{
|
|
am->running = list_peek_head_type(&ready->cmds,
|
|
struct action_cmd_s, node);
|
|
}
|
|
|
|
if ((am->running->op == CMD_OP_AND && ready->prev_ret != 0) ||
|
|
(am->running->op == CMD_OP_OR && ready->prev_ret == 0))
|
|
{
|
|
init_debug("Skipping command due to op (prev_ret %d)",
|
|
ready->prev_ret);
|
|
init_action_reap_command(am, ready->prev_ret);
|
|
return 0;
|
|
}
|
|
|
|
#if defined(CONFIG_SYSTEM_INIT_ACTION_WARN_SLOW) && \
|
|
CONFIG_SYSTEM_INIT_ACTION_WARN_SLOW > 0
|
|
clock_gettime(CLOCK_MONOTONIC, &am->time_run);
|
|
#endif
|
|
ret = init_builtin_run(am, am->running->argc, am->running->argv);
|
|
if (ret > 0)
|
|
{
|
|
am->pid_running = ret;
|
|
}
|
|
else
|
|
{
|
|
init_action_reap_command(am, ret);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void init_action_reap_command(FAR struct action_manager_s *am, int ret)
|
|
{
|
|
FAR struct action_s *ready = list_peek_head_type(&am->ready_actions,
|
|
struct action_s,
|
|
ready_node);
|
|
|
|
#if defined(CONFIG_SYSTEM_INIT_ACTION_WARN_SLOW) && \
|
|
CONFIG_SYSTEM_INIT_ACTION_WARN_SLOW > 0
|
|
struct timespec time;
|
|
int ms;
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &time);
|
|
clock_timespec_subtract(&time, &am->time_run, &time);
|
|
ms = TIMESPEC2MS(time);
|
|
if (ms > CONFIG_SYSTEM_INIT_ACTION_WARN_SLOW)
|
|
{
|
|
if (am->pid_running <= 0)
|
|
{
|
|
init_warn("command '%s' took %d ms", am->running->argv[0], ms);
|
|
}
|
|
else
|
|
{
|
|
init_warn("command '%s' pid %d took %d ms", am->running->argv[0],
|
|
am->pid_running, ms);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
am->pid_running = -1;
|
|
ready->prev_ret = ret;
|
|
if (list_is_tail(&ready->cmds, &am->running->node))
|
|
{
|
|
am->running = NULL;
|
|
list_delete(&ready->ready_node);
|
|
}
|
|
else
|
|
{
|
|
am->running = list_next_entry(am->running, struct action_cmd_s, node);
|
|
}
|
|
}
|
|
|
|
int init_action_parse(FAR const struct parser_s *parser,
|
|
bool create, FAR char *buf)
|
|
{
|
|
FAR struct action_manager_s *am = parser->priv;
|
|
FAR char *argv[2 * CONFIG_SYSTEM_INIT_ACTION_EVENTS_MAX];
|
|
FAR struct action_s *a;
|
|
int ret;
|
|
|
|
if (create)
|
|
{
|
|
ret = init_parse_arguments(buf, false, nitems(argv), argv);
|
|
if (ret < 2)
|
|
{
|
|
init_err("invalid argument: %s", buf);
|
|
return -EINVAL;
|
|
}
|
|
|
|
a = calloc(1, sizeof(*a));
|
|
if (a == NULL)
|
|
{
|
|
init_err("alloc");
|
|
return -errno;
|
|
}
|
|
|
|
list_initialize(&a->cmds);
|
|
|
|
while (--ret > 0)
|
|
{
|
|
int n;
|
|
|
|
if (!strncmp(argv[ret], ACTION_SECTION_PROP_PREFIX,
|
|
strlen(ACTION_SECTION_PROP_PREFIX)))
|
|
{
|
|
n = parse_event(argv[ret], a->events, nitems(a->events));
|
|
if (n < 0)
|
|
{
|
|
free(a);
|
|
return n;
|
|
}
|
|
}
|
|
else if (strncmp(argv[ret], ACTION_SECTION_AND_PREFIX,
|
|
strlen(ACTION_SECTION_AND_PREFIX)))
|
|
{
|
|
char tmp[CONFIG_SYSTEM_INIT_RC_LINE_MAX];
|
|
|
|
sprintf(tmp, ":%s=%s", ACTION_PROP_KEY_DEFAULT, argv[ret]);
|
|
n = parse_event(tmp, a->events, nitems(a->events));
|
|
if (n < 0)
|
|
{
|
|
free(a);
|
|
return n;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
list_add_tail(&am->actions, &a->node);
|
|
init_debug("add action %p(%s) to manager %p", a, argv[1], am);
|
|
}
|
|
else
|
|
{
|
|
a = list_last_entry(&am->actions, struct action_s, node);
|
|
ret = parse_command(buf, &a->cmds);
|
|
if (ret < 0)
|
|
{
|
|
init_err("failed to parse command line: %s", buf);
|
|
return ret;
|
|
}
|
|
|
|
init_debug("add command line to action %p", a);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_SYSTEM_INIT_DEBUG
|
|
void init_dump_actions(FAR struct list_node *head)
|
|
{
|
|
FAR struct action_s *action;
|
|
|
|
init_debug("== Dump Actions ==");
|
|
list_for_every_entry(head, action, struct action_s, node)
|
|
{
|
|
init_dump_action(action);
|
|
}
|
|
}
|
|
#endif
|