* apps/system/init/service.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 <nuttx/clock.h>
#include <sys/boardctl.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <signal.h>
#include <spawn.h>
#include <sys/param.h>
#include "init.h"
#include "parser.h"
#include "service.h"
* Pre-processor Definitions
****************************************************************************/
#define SYSTEM_INIT_SERVICE_GENTLE_KILL_TIMEOUT 200
#define check_flags(s, f) ((s)->flags & (f))
#ifdef CONFIG_SYSTEM_INIT_DEBUG
# define dump_flags(fmt, flags) \
do \
{ \
size_t _i; \
for (_i = 0; _i < nitems(g_flag_str); _i++) \
{ \
if (g_flag_str[_i].flag & (flags)) \
{ \
init_debug(fmt, g_flag_str[_i].str); \
} \
} \
} \
while(0)
#else
# define dump_flags(...)
#endif
* Private Types
****************************************************************************/
struct cmd_map_s
{
FAR const char *cmd;
uint8_t minargs;
uint8_t maxargs;
int (*func)(FAR struct service_manager_s *, int, FAR char **);
};
#ifdef CONFIG_SYSTEM_INIT_DEBUG
struct flag_str_s
{
uint32_t flag;
FAR const char *str;
};
#endif
* Private Function Prototypes
****************************************************************************/
static int option_class(FAR struct service_manager_s *sm,
int argc, FAR char **argv);
static int option_gentle_kill(FAR struct service_manager_s *sm,
int argc, FAR char **argv);
static int option_restart_period(FAR struct service_manager_s *sm,
int argc, FAR char **argv);
static int option_override(FAR struct service_manager_s *sm,
int argc, FAR char **argv);
static int option_oneshot(FAR struct service_manager_s *sm,
int argc, FAR char **argv);
#ifdef CONFIG_BOARDCTL_RESET
static int option_reboot_on_failure(FAR struct service_manager_s *sm,
int argc, FAR char **argv);
#endif
* Private Data
****************************************************************************/
static const struct cmd_map_s g_option[] =
{
{"class", 2, 99, option_class},
{"gentle_kill", 1, 1, option_gentle_kill},
{"restart_period", 2, 2, option_restart_period},
{"override", 1, 1, option_override},
{"oneshot", 1, 1, option_oneshot},
#ifdef CONFIG_BOARDCTL_RESET
{"reboot_on_failure", 2, 2, option_reboot_on_failure},
#endif
};
#ifdef CONFIG_SYSTEM_INIT_DEBUG
static const struct flag_str_s g_flag_str[] =
{
{SVC_DISABLED, "disabled"},
{SVC_ONESHOT, "oneshot"},
{SVC_RUNNING, "running"},
{SVC_RESTARTING, "restarting"},
{SVC_GENTLE_KILL, "gentle_kill"},
{SVC_REMOVE, "remove"},
{SVC_SIGKILL, "sigkill"},
{SVC_OVERRIDE, "override"},
};
#endif
* Private Functions
****************************************************************************/
static void add_flags(FAR struct service_s *service, uint32_t flags)
{
init_debug("service '%s' flag 0x%" PRIx32 " add 0x%" PRIx32 "",
service->argv[1], service->flags, flags);
dump_flags(" +flag '%s'", flags);
service->flags |= flags;
}
static void remove_flags(FAR struct service_s *service, uint32_t flags)
{
init_debug("service '%s' flag 0x%" PRIx32 " add 0x%" PRIx32 "",
service->argv[1], service->flags, flags);
dump_flags(" -flag '%s'", flags);
service->flags &= ~flags;
}
static int kill_service(FAR struct service_s *service, int signo)
{
int ret;
ret = kill(service->pid, signo);
if (ret < 0)
{
ret = -errno;
if (ret == -ESRCH)
{
init_service_reap(service, 0);
}
}
init_log(ret < 0 ? LOG_ERR : LOG_WARNING,
"sent signal %d to service '%s' pid %d %d",
signo, service->argv[1], service->pid, ret);
return ret;
}
static void remove_service(FAR struct service_s *service)
{
FAR struct service_class_s *class;
FAR struct service_class_s *tmp;
int i;
init_warn("removing service '%s' ...", service->argv[1]);
list_for_every_entry_safe(&service->classes, class, tmp,
struct service_class_s, node)
{
list_delete(&class->node);
free(class);
}
for (i = 0; i < service->argc; i++)
{
free(service->argv[i]);
}
list_delete(&service->node);
free(service);
}
static int option_class(FAR struct service_manager_s *sm,
int argc, FAR char **argv)
{
FAR struct service_s *s = list_last_entry(&sm->services, struct service_s,
node);
size_t len = strlen(argv[1]) + 1;
FAR struct service_class_s *c;
list_for_every_entry(&s->classes, c, struct service_class_s, node)
{
if (!strcmp(c->name, argv[1]))
{
return 0;
}
}
c = malloc(sizeof(*c) + len);
if (!c)
{
init_err("alloc class");
return -errno;
}
memcpy(c->name, argv[1], len);
list_add_tail(&s->classes, &c->node);
return 0;
}
static int option_gentle_kill(FAR struct service_manager_s *sm,
int argc, FAR char **argv)
{
FAR struct service_s *s = list_last_entry(&sm->services, struct service_s,
node);
add_flags(s, SVC_GENTLE_KILL);
return 0;
}
static int option_restart_period(FAR struct service_manager_s *sm,
int argc, FAR char **argv)
{
FAR struct service_s *s = list_last_entry(&sm->services, struct service_s,
node);
s->restart_period = atoi(argv[1]);
return 0;
}
static int option_override(FAR struct service_manager_s *sm,
int argc, FAR char **argv)
{
FAR struct service_s *s = list_last_entry(&sm->services, struct service_s,
node);
add_flags(s, SVC_OVERRIDE);
return 0;
}
static int option_oneshot(FAR struct service_manager_s *sm,
int argc, FAR char **argv)
{
FAR struct service_s *s = list_last_entry(&sm->services, struct service_s,
node);
add_flags(s, SVC_ONESHOT);
return 0;
}
#ifdef CONFIG_BOARDCTL_RESET
static int option_reboot_on_failure(FAR struct service_manager_s *sm,
int argc, FAR char **argv)
{
FAR struct service_s *s = list_last_entry(&sm->services, struct service_s,
node);
s->reset_reason = atoi(argv[1]);
return 0;
}
#endif
* Public Functions
****************************************************************************/
* Name: init_service_refresh
*
* Description:
* Check if any services need to be restarted, force terminate(SIGKILL), or
* deleted.
*
* Input Parameters:
* sm - Instance of Service Manager
*
* Returned Value:
* The expected time interval until the next call is returned, INT_MAX for
* block indefinitely;
****************************************************************************/
int init_service_refresh(FAR struct service_manager_s *sm)
{
FAR struct service_s *service;
FAR struct service_s *tmp;
struct timespec diff;
struct timespec cur;
int min = INT_MAX;
int ms;
clock_gettime(CLOCK_MONOTONIC, &cur);
list_for_every_entry_safe(&sm->services, service, tmp, struct service_s,
node)
{
if (check_flags(service, SVC_RESTARTING))
{
clock_timespec_subtract(&cur, &service->time_started, &diff);
ms = TIMESPEC2MS(diff);
if (ms >= service->restart_period)
{
init_service_start(service);
continue;
}
min = MIN(min, service->restart_period - ms);
}
else if (check_flags(service, SVC_RUNNING) &&
check_flags(service, SVC_GENTLE_KILL) &&
check_flags(service, SVC_DISABLED))
{
if (check_flags(service, SVC_SIGKILL))
{
continue;
}
clock_timespec_subtract(&cur, &service->time_kill, &diff);
ms = TIMESPEC2MS(diff);
if (ms >= SYSTEM_INIT_SERVICE_GENTLE_KILL_TIMEOUT)
{
init_warn("service '%s' pid %d timeout for gentle kill",
service->argv[1], service->pid);
kill_service(service, SIGKILL);
add_flags(service, SVC_SIGKILL);
continue;
}
min = MIN(min, SYSTEM_INIT_SERVICE_GENTLE_KILL_TIMEOUT - ms);
}
else if (check_flags(service, SVC_REMOVE) &&
check_flags(service, SVC_DISABLED) &&
!check_flags(service, SVC_RUNNING))
{
remove_service(service);
}
}
return min;
}
FAR struct service_s *
init_service_find_by_name(FAR struct service_manager_s *sm,
FAR const char *name)
{
FAR struct service_s *service;
list_for_every_entry(&sm->services, service, struct service_s, node)
{
if (!strcmp(name, service->argv[1]))
{
return service;
}
}
return NULL;
}
FAR struct service_s *
init_service_find_by_pid(FAR struct service_manager_s *sm, const int pid)
{
FAR struct service_s *service;
list_for_every_entry(&sm->services, service, struct service_s, node)
{
if (pid == service->pid)
{
return service;
}
}
return NULL;
}
void init_service_reap(FAR struct service_s *service, int status)
{
#ifdef CONFIG_BOARDCTL_RESET
int ret;
if (status && service->reset_reason >= 0)
{
init_err("reboot on failure of service '%s' reason %d",
service->argv[1], service->reset_reason);
ret = boardctl(BOARDIOC_RESET, service->reset_reason);
if (ret < 0)
{
init_err("reset failed %d", errno);
}
}
#else
UNUSED(status);
#endif
remove_flags(service, SVC_RUNNING);
if (check_flags(service, SVC_ONESHOT))
{
add_flags(service, SVC_DISABLED | SVC_REMOVE);
}
if (!check_flags(service, SVC_DISABLED))
{
add_flags(service, SVC_RESTARTING);
}
}
int init_service_start(FAR struct service_s *service)
{
int ret;
int pid;
init_debug("starting service '%s' ...", service->argv[1]);
if (check_flags(service, SVC_RUNNING))
{
init_info("service '%s' already started pid %d", service->argv[1],
service->pid);
return service->pid;
}
ret = posix_spawnp(&pid, service->argv[2], NULL, NULL, &service->argv[2],
environ);
if (ret != 0)
{
init_err("starting service '%s': %d", service->argv[1], ret);
init_service_reap(service, ret);
return -ret;
}
service->pid = pid;
clock_gettime(CLOCK_MONOTONIC, &service->time_started);
add_flags(service, SVC_RUNNING);
remove_flags(service, SVC_RESTARTING);
remove_flags(service, SVC_DISABLED);
init_info("started service '%s' pid %d", service->argv[1], service->pid);
return service->pid;
}
int init_service_stop(FAR struct service_s *service)
{
init_info("stopping service '%s' ...", service->argv[1]);
if (check_flags(service, SVC_RUNNING | SVC_RESTARTING))
{
if (check_flags(service, SVC_DISABLED))
{
init_warn("service '%s' is exiting", service->argv[1]);
return -EINPROGRESS;
}
}
else
{
init_warn("service '%s' is not running", service->argv[1]);
return -ESRCH;
}
add_flags(service, SVC_DISABLED);
if (check_flags(service, SVC_RESTARTING))
{
remove_flags(service, SVC_RESTARTING);
}
if (check_flags(service, SVC_GENTLE_KILL))
{
init_debug("gentel kill");
remove_flags(service, SVC_SIGKILL);
kill_service(service, SIGTERM);
clock_gettime(CLOCK_MONOTONIC, &service->time_kill);
return 0;
}
return kill_service(service, SIGKILL);
}
int init_service_start_by_class(FAR struct service_manager_s *sm,
FAR const char *name)
{
FAR struct service_class_s *class;
FAR struct service_s *service;
int ret;
list_for_every_entry(&sm->services, service, struct service_s, node)
{
list_for_every_entry(&service->classes, class, struct service_class_s,
node)
{
if (!strcmp(name, class->name))
{
ret = init_service_start(service);
if (ret < 0)
{
return ret;
}
break;
}
}
}
return 0;
}
int init_service_stop_by_class(FAR struct service_manager_s *sm,
FAR const char *name)
{
FAR struct service_class_s *class;
FAR struct service_s *service;
int ret;
list_for_every_entry(&sm->services, service, struct service_s, node)
{
list_for_every_entry(&service->classes, class, struct service_class_s,
node)
{
if (!strcmp(name, class->name))
{
ret = init_service_stop(service);
if (ret < 0)
{
return ret;
}
break;
}
}
}
return 0;
}
int init_service_parse(FAR const struct parser_s *parser,
bool create, FAR char *buf)
{
FAR char *argv[CONFIG_SYSTEM_INIT_SERVICE_ARGS_MAX];
FAR struct service_manager_s *sm = parser->priv;
FAR struct service_s *s;
int argc;
size_t i;
if (create)
{
s = calloc(1, sizeof(*s));
if (!s)
{
init_err("alloc");
return -errno;
}
s->argc = init_parse_arguments(buf, true, nitems(s->argv), s->argv);
if (s->argc < 3)
{
init_err("get arguments %d", s->argc);
free(s);
return -EINVAL;
}
s->restart_period = CONFIG_SYSTEM_INIT_SERVICE_RESTART_PERIOD;
#ifdef CONFIG_BOARDCTL_RESET
s->reset_reason = -1;
#endif
list_initialize(&s->classes);
list_add_tail(&sm->services, &s->node);
}
else
{
argc = init_parse_arguments(buf, false, nitems(argv), argv);
for (i = 0; i < nitems(g_option); i++)
{
if (!strcmp(g_option[i].cmd, argv[0]))
{
init_info("apply option[%zu] %p for %s", i, g_option[i].func,
argv[0]);
if (argc < g_option[i].minargs || argc > g_option[i].maxargs)
{
break;
}
init_dump_args(argc, argv);
return g_option[i].func(sm, argc, argv);
}
}
init_err("'%s': unknown option / invalid argument", argv[0]);
return -EINVAL;
}
return 0;
}
int init_service_check(FAR const struct parser_s *parser)
{
FAR struct service_manager_s *sm = parser->priv;
FAR struct service_s *tmp;
FAR struct service_s *s;
list_for_every_entry(&sm->services, s, struct service_s, node)
{
tmp = list_next_entry(s, struct service_s, node);
list_for_every_entry_from(&sm->services, tmp, struct service_s, node)
{
if (!strcmp(s->argv[1], tmp->argv[1]))
{
if (!check_flags(tmp, SVC_OVERRIDE))
{
init_err("redefined service '%s'", tmp->argv[1]);
init_dump_service(s);
init_dump_service(tmp);
return -EEXIST;
}
init_info("remove duplicate definition of service '%s'",
tmp->argv[1]);
add_flags(s, SVC_DISABLED | SVC_REMOVE);
}
}
}
return 0;
}
#ifdef CONFIG_SYSTEM_INIT_DEBUG
void init_dump_service(FAR struct service_s *s)
{
FAR struct service_class_s *c;
int i;
init_debug("Service %p name '%s' path '%s'", s, s->argv[1], s->argv[2]);
init_debug(" pid: %d", s->pid);
init_debug(" arguments:");
for (i = 0; s->argv[i] && i < nitems(s->argv); i++)
{
init_debug(" [%d] '%s'", i, s->argv[i]);
}
init_debug(" classes:");
list_for_every_entry(&s->classes, c, struct service_class_s, node)
{
init_debug(" '%s'", c->name);
}
init_debug(" restart_period: %d", s->restart_period);
#ifdef CONFIG_BOARDCTL_RESET
init_debug(" reboot_on_failure: %d", s->reset_reason);
#endif
if (s->flags)
{
init_debug(" flags:");
dump_flags(" '%s'", s->flags);
}
}
void init_dump_services(FAR struct list_node *head)
{
FAR struct service_s *service;
init_debug("== Dump Services ==");
list_for_every_entry(head, service, struct service_s, node)
{
init_dump_service(service);
}
}
#endif