Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions system/nxinit/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,20 @@ config SYSTEM_NXINIT_SERVICE_RESTART_PERIOD
int "Service restart period in ms"
default 5000

config SYSTEM_NXINIT_CONSOLE_DEV
string "Default console device of a service"
default "/dev/console"
---help---
The device used as stdin/stdout/stderr of a service declared with the
option "console" and no explicit device, i.e. "console" rather than
"console <device>".

This is needed by services which expect a controlling terminal but do
not open one themselves (e.g. plain "sh"), and is the only way to get
a console when the console device is not available at the time the
file descriptors of the idle task are set up, as is the case for a USB
gadget console.

comment "NXInit Testing"

config SYSTEM_NXINIT_TEST
Expand Down
127 changes: 123 additions & 4 deletions system/nxinit/service.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <signal.h>
#include <spawn.h>
#include <sys/param.h>
#include <unistd.h>

#include "init.h"
#include "parser.h"
Expand Down Expand Up @@ -100,6 +102,8 @@ 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);
static int option_console(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);
Expand All @@ -116,6 +120,7 @@ static const struct cmd_map_s g_option[] =
{"restart_period", 2, 2, option_restart_period},
{"override", 1, 1, option_override},
{"oneshot", 1, 1, option_oneshot},
{"console", 1, 2, option_console},
#ifdef CONFIG_BOARDCTL_RESET
{"reboot_on_failure", 2, 2, option_reboot_on_failure},
#endif
Expand All @@ -128,6 +133,7 @@ static const struct flag_str_s g_flag_str[] =
{SVC_ONESHOT, "oneshot"},
{SVC_RUNNING, "running"},
{SVC_RESTARTING, "restarting"},
{SVC_CONSOLE, "console"},
{SVC_GENTLE_KILL, "gentle_kill"},
{SVC_REMOVE, "remove"},
{SVC_SIGKILL, "sigkill"},
Expand Down Expand Up @@ -197,6 +203,7 @@ static void remove_service(FAR struct service_s *service)
free(service->argv[i]);
}

free(service->console);
list_delete(&service->node);
free(service);
}
Expand Down Expand Up @@ -273,12 +280,50 @@ static int option_oneshot(FAR struct service_manager_s *sm,
return 0;
}

/****************************************************************************
* Name: option_console
*
* Description:
* Handle the service option "console [<device>]". The service is given
* 'device' (CONFIG_SYSTEM_NXINIT_CONSOLE_DEV if omitted) as its stdin,
* stdout and stderr, so that a service which does not open a console
* device on its own still gets a working console.
*
****************************************************************************/

static int option_console(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_CONSOLE);

if (argc > 1)
{
/* 'argv' points into the parser line buffer, which is reused for the
* next line, so the device name must be duplicated here.
*/

free(s->console);
s->console = strdup(argv[1]);
if (s->console == NULL)
{
init_err("Alloc console device");
return -ENOMEM;
}
}

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;
}
Expand Down Expand Up @@ -322,7 +367,11 @@ int init_service_refresh(FAR struct service_manager_s *sm)
ms = TIMESPEC2MS(diff);
if (ms >= service->restart_period)
{
init_service_start(service);
if (init_service_start(service) < 0)
{
min = MIN(min, service->restart_period);
}

continue;
}

Expand Down Expand Up @@ -425,8 +474,59 @@ void init_service_reap(FAR struct service_s *service, int status)
}
}

/****************************************************************************
* Name: console_file_actions
*
* Description:
* Build the spawn file actions which redirect the stdio of a service
* flagged SVC_CONSOLE to its console device. The actions are performed
* in the context of the new task, so the stdio of NxInit itself is left
* untouched.
*
****************************************************************************/

static int console_file_actions(FAR posix_spawn_file_actions_t *actions,
FAR struct service_s *service)
{
FAR const char *dev = service->console ?
service->console : CONFIG_SYSTEM_NXINIT_CONSOLE_DEV;
int ret;

ret = posix_spawn_file_actions_init(actions);
if (ret != 0)
{
init_err("posix_spawn_file_actions_init %d", ret);
return -ret;
}

ret = posix_spawn_file_actions_addopen(actions, STDIN_FILENO, dev,
O_RDWR, 0);
if (ret == 0)
{
ret = posix_spawn_file_actions_adddup2(actions, STDIN_FILENO,
STDOUT_FILENO);
}

if (ret == 0)
{
ret = posix_spawn_file_actions_adddup2(actions, STDIN_FILENO,
STDERR_FILENO);
}

if (ret != 0)
{
init_err("Add console '%s' file action %d", dev, ret);
posix_spawn_file_actions_destroy(actions);
return -ret;
}

return 0;
}

int init_service_start(FAR struct service_s *service)
{
FAR posix_spawn_file_actions_t *pactions = NULL;
posix_spawn_file_actions_t actions;
posix_spawnattr_t attr;
sigset_t mask;
int ret;
Expand Down Expand Up @@ -465,9 +565,29 @@ int init_service_start(FAR struct service_s *service)
return -ret;
}

ret = posix_spawnp(&pid, service->argv[2], NULL, &attr, &service->argv[2],
environ);
clock_gettime(CLOCK_MONOTONIC, &service->time_started);

if (check_flags(service, SVC_CONSOLE))
{
ret = console_file_actions(&actions, service);
if (ret < 0)
{
posix_spawnattr_destroy(&attr);
init_service_reap(service, -ret);
return ret;
}

pactions = &actions;
}

ret = posix_spawnp(&pid, service->argv[2], pactions, &attr,
&service->argv[2], environ);
posix_spawnattr_destroy(&attr);
if (pactions != NULL)
{
posix_spawn_file_actions_destroy(pactions);
}

if (ret != 0)
{
init_err("Starting service '%s': %d", service->argv[1], ret);
Expand All @@ -476,7 +596,6 @@ int init_service_start(FAR struct service_s *service)
}

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);
Expand Down
7 changes: 7 additions & 0 deletions system/nxinit/service.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#define SVC_ONESHOT (1 << 1) /* do not restart on exit */
#define SVC_RUNNING (1 << 2) /* currently active */
#define SVC_RESTARTING (1 << 3) /* waiting to restart */
#define SVC_CONSOLE (1 << 4) /* requires a console as its stdio */

/* This service should be stopped with SIGTERM instead of SIGKILL.
* Will still be SIGKILLed after timeout period of 200 ms.
Expand Down Expand Up @@ -105,6 +106,12 @@ struct service_s
int restart_period;
pid_t pid;

/* The device given by the service option "console". NULL means that the
* default console device is used. Only meaningful with SVC_CONSOLE.
*/

FAR char *console;

/* The "target" of service option "reboot_on_failure" */

#ifdef CONFIG_BOARDCTL_RESET
Expand Down
1 change: 1 addition & 0 deletions system/nxinit/test/test_nxinit.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ int main(int argc, FAR char *argv[])
cmocka_unit_test(test_nxinit_service_duplicate_conflict),
cmocka_unit_test(test_nxinit_service_override_replaces_duplicate),
cmocka_unit_test(test_nxinit_service_args_max_boundary),
cmocka_unit_test(test_nxinit_service_console_option),
};

return cmocka_run_group_tests(nxinit_tests, test_nxinit_group_setup,
Expand Down
1 change: 1 addition & 0 deletions system/nxinit/test/test_nxinit.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,6 @@ void test_nxinit_action_event_and_semantics(FAR void **state);
void test_nxinit_service_duplicate_conflict(FAR void **state);
void test_nxinit_service_override_replaces_duplicate(FAR void **state);
void test_nxinit_service_args_max_boundary(FAR void **state);
void test_nxinit_service_console_option(FAR void **state);

#endif /* __APPS_SYSTEM_NXINIT_TEST_TEST_NXINIT_H */
63 changes: 63 additions & 0 deletions system/nxinit/test/test_nxinit_service.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ static void service_manager_free_all(FAR struct service_manager_s *sm)
free(s->argv[i]);
}

free(s->console);
list_delete(&s->node);
free(s);
}
Expand Down Expand Up @@ -249,3 +250,65 @@ void test_nxinit_service_args_max_boundary(FAR void **state)

#undef NARGS_AT_LIMIT
}

/****************************************************************************
* Name: test_nxinit_service_console_option
*
* Description:
* The "console" option flags the service with SVC_CONSOLE. Without an
* argument the default console device is used (console == NULL); with an
* argument the device name is duplicated into the service, since the
* parser reuses its line buffer for the next line.
****************************************************************************/

void test_nxinit_service_console_option(FAR void **state)
{
struct service_manager_s sm;
struct parser_s parser =
{
"service", init_service_parse, init_service_check, &sm
};

char decl1[] = "service console1 /bin/sh";
char opt_default[] = " console";
char decl2[] = "service console2 /bin/sh";
char opt_device[] = " console /dev/ttyACM0";
char decl3[] = "service plain /bin/sh";
FAR struct service_s *s;

service_manager_init(&sm);

/* "console" without an argument: flagged, default device. */

assert_int_equal(init_service_parse(&parser, true, decl1), 0);
assert_int_equal(init_service_parse(&parser, false, opt_default), 0);

s = list_last_entry(&sm.services, struct service_s, node);
assert_int_equal(s->flags & SVC_CONSOLE, SVC_CONSOLE);
assert_null(s->console);

/* "console <device>": flagged, device duplicated (not aliased into the
* caller's line buffer, which is reused for the next line).
*/

assert_int_equal(init_service_parse(&parser, true, decl2), 0);
assert_int_equal(init_service_parse(&parser, false, opt_device), 0);

s = list_last_entry(&sm.services, struct service_s, node);
assert_int_equal(s->flags & SVC_CONSOLE, SVC_CONSOLE);
assert_non_null(s->console);
assert_string_equal(s->console, "/dev/ttyACM0");
assert_ptr_not_equal(s->console, opt_device + 10);

/* A service without the option keeps its stdio untouched. */

assert_int_equal(init_service_parse(&parser, true, decl3), 0);

s = list_last_entry(&sm.services, struct service_s, node);
assert_int_equal(s->flags & SVC_CONSOLE, 0);
assert_null(s->console);

assert_int_equal(init_service_check(&parser), 0);

service_manager_free_all(&sm);
}
Loading