* 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';
}
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