The preset kvdb service defined in parser.c conflicts with user-defined
kvdb service in board-level init.rc, causing:
Error redefined service 'kvdb'
Add SVC_FALLBACK flag and fallback service option. When a service
is marked as fallback, it will be silently ignored if another
service with the same name already exists. This is the semantic
opposite of override:
- override: new definition replaces old
- fallback: new definition yields to old
- old has fallback + new arrives: old yields to new
If neither flag is set, duplicate service names still produce
EEXIST error as before.
Mark the preset kvdb service as fallback so that board-specific
init.rc can freely define its own kvdb service without conflict.
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
RPTUNIOC_START returns the (positive) pid of the rptun kernel thread
in async mode (CONFIG_RPTUN_START_SYNC unset). The action engine
treats any positive builtin return value as the pid of a spawned
child and waitpid()s on it (action.c: "if (ret > 0) pid_running =
ret"). The rptun thread is a detached kthread, never a child of init,
so that wait blocks the whole action queue forever and "on init" /
console never run. Normalize a successful start to 0.
Assisted-by: OpenCode:claude-sonnet-5
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
BL uses init framework (not NSH), so the NSH rptun command is not
available. Add rptun as an init builtin command that supports
start and stop subcommands. Also add a generic unlink builtin
command for removing device nodes.
Usage in init.bl.rc:
rptun stop /dev/rptun/corecs
unlink /dev/rptun/corecs
rptun start /dev/rptun/corecs
- rptun start/stop: open device, ioctl(RPTUNIOC_START/STOP), close
- unlink: generic command to unlink any file/device node
Signed-off-by: wangyongrong <wangyongrong@xiaomi.com>
usbtrace_enable(TRACE_BITSET) was copied from nsh's CONFIG_USBDEV_TRACE
block without also copying nsh's local #define TRACE_BITSET or the
<nuttx/usb/usbdev_trace.h> include it needs. TRACE_BITSET has no
built-in definition; every other CONFIG_USBDEV_TRACE caller in the
tree (nsh, composite, cdcacm, usbmsc) defines its own. This compiled
fine as long as CONFIG_SYSTEM_NXINIT and CONFIG_USBDEV_TRACE were never
both on for the same board; the two combined for the first time and
init.c failed to build with 'TRACE_BITSET' undeclared.
Add the missing include and inline the same error-only bitset those
other callers fall back to when none of their granular trace options
are enabled, since this file has no such granular Kconfig of its own.
Assisted-by: OpenCode:claude-sonnet-5
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
Syslog may be output via services such as DFX. When debugging the init
component (e.g., service startup failures, including DFX-related
exceptions), syslog may not be output properly.
Add a debug Kconfig option SYSTEM_NXINIT_STDOUT that redirects all init
log macros (init_debug/info/warn/err) to printf instead of syslog.
This is useful for early boot debugging when syslog is not yet
available or serial console shows no output.
Introduce init_log_output() macro as the common log backend, selected
at compile time between printf (with appended newline) and syslog.
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
Services started by nxinit (e.g. "sh") do not open a console device on
their own, unlike nsh_main, which explicitly does so via
nsh_consolemain()/nsh_waitusbready() for USB gadget consoles
(CDC-ACM/PL2303). When such a board switches its top-level init from
nsh_main to nxinit, no code path ever registers/connects the USB
console gadget, and a service that just execs a plain "sh" inherits
whatever (invalid, for a USB gadget console not yet opened at the time
the idle task file descriptors are set up) stdio nxinit itself has.
Add a "console [<device>]" service option: a service declared with it
gets the given device (CONFIG_SYSTEM_NXINIT_CONSOLE_DEV, "/dev/console"
by default, if no device is given) opened and dup'd onto its stdin,
stdout and stderr via posix_spawn_file_actions before it is spawned.
This does not depend on nsh being enabled at all.
For a USB gadget console, the device does not exist until the gadget
is actually registered; boards using one are expected to bring it up
themselves before any service using "console" is started (e.g. via an
"exec -- sercon" action in their init.rc, since apps/system/cdcacm
already implements exactly that registration step and does not depend
on nxinit or nsh either).
console_file_actions() runs after the previous commit's time_started
update, so a failure to build the console's file actions is covered by
the same up to date timestamp - no separate clock_gettime() call is
needed on this failure path.
Covered by a new unit test, test_nxinit_service_console_option, that
exercises the option with and without an explicit device, and confirms
services without the option are left untouched.
Assisted-by: Kiro:claude-sonnet-5
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
init_service_refresh() ignored the return value of init_service_start().
If spawning a restarting service failed for any reason (e.g.
posix_spawnp() itself failing), the service stayed SVC_RESTARTING
forever with nothing left to re-arm its retry timer: this function's
return value drives the poll timeout in the caller's event loop
(main()), and a failed spawn does not fork a child, so there is no
SIGCHLD either to wake it up some other way. If this service happens
to be the only pending timer, the poll blocks indefinitely and the
service is never attempted again.
Check the return value and, on failure, feed the service restart
period into the poll timeout computed by this function, the same way
a successfully started/still-restarting service already does.
Also move the CLOCK_MONOTONIC read that updates a service's
time_started from after a successful spawn to before the spawn is
even attempted, so that time_started stays current on a failed spawn
too - otherwise, once woken up (by the fix above or by an unrelated
event), a repeatedly failing service would look permanently overdue
(elapsed time computed against a stale timestamp) and get retried
immediately regardless of its restart_period.
Assisted-by: Kiro:claude-sonnet-5
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
nxstyle flags a missing blank line between the declaration of "s" and
the first statement in option_reboot_on_failure(); pre-existing,
unrelated to any behavioral change here.
Assisted-by: Kiro:claude-sonnet-5
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
Add a test/ subdirectory (mirroring apps/system/uorb/test/) with
cmocka-based unit tests covering the NxInit logic most prone to
regression:
- init_parse_arguments(): plain/quoted arguments, "--" separator vs.
"--option" long options (regression coverage for a previously fixed
bug), argv-capacity truncation (asserting the exact folded contents
of the last slot, not just its presence).
- init_parse_config_file()/init_parse_config_lines()/
init_parse_config_buffer(): section routing, blank/whitespace-only
line skipping, unknown-section rejection, over-length line rejection,
and a line straddling two read-buffer refills, exercised through both
the file-based and buffer-based entry points.
- Action event matching: exact match, invert (!=), fnmatch wildcards,
and AND semantics across multiple events per action.
- Service conflict detection: duplicate service name rejection,
override replacing an earlier duplicate, and the SERVICE_ARGS_MAX
boundary built dynamically from CONFIG_SYSTEM_NXINIT_SERVICE_ARGS_MAX
rather than a hardcoded value.
Test sources compile action.c/parser.c/service.c a second time into a
separate nxinit_unit_test program, gated behind new
CONFIG_SYSTEM_NXINIT_TEST (depends on TESTING_CMOCKA); the default init
program is unaffected. The CMake path builds a dedicated
nxinit_unit_test target (with test/test_nxinit.c placed first in SRCS
so nuttx_add_application() renames its main() correctly); the Make path
appends the test sources into the shared CSRCS list.
Supporting bits required to make the suite exercise the real code:
- init_parse_config_buffer() is declared in parser.h and made
non-static so the buffer-based boundary test can call it directly,
alongside the existing init_parse_config_file() entry point.
- CONFIG_SYSTEM_NXINIT_ACTION_EVENTS_MAX default is raised from 1 to 2
so an action can carry more than one event ("on evA && evB"), which
the multi-event AND-semantics test exercises; a single event slot
made that test dead code.
- CONFIG_SYSTEM_NXINIT_TEST_STACKSIZE defaults to 8192: several parser
test cases build multi-hundred-byte stack buffers on top of cmocka's
own overhead, and the previous DEFAULT_TASK_STACKSIZE (2048)
overflowed the test task's stack silently on real hardware (no crash
dump, no watchdog reset, output just stopped) partway through the
suite.
Testing:
Built via `make CROSSDEV=riscv-none-elf-` for
esp32p4-pico-wifi-wareshare:nsh (CONFIG_SYSTEM_NXINIT_TEST=y) and ran
nxinit_unit_test on real esp32p4-pico-wifi-wareshare hardware over
UART:
nsh> nxinit_unit_test
[==========] nxinit_tests: Running 18 test(s).
...
[==========] nxinit_tests: 18 test(s) run.
[ PASSED ] 18 test(s).
nxstyle clean on all touched files.
Assisted-by: GitHubCopilot:claude-sonnet-5
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
init_parse_config_buffer() computed the per-refill copy length as
MIN(len - off, sizeof(tmp)) without subtracting the 'n' leftover bytes
already held at the front of 'tmp' from a previous refill, so
memcpy(&tmp[n], ..., r) could write past the end of tmp[]. The
file-based twin, init_parse_config_file(), already gets this right
(read(fd, &buf[n], sizeof(buf) - n)).
Reproduced locally with an AddressSanitizer host harness feeding the
real 95-byte builtin "preset" rc content through
init_parse_config_buffer() at CONFIG_SYSTEM_NXINIT_RC_LINE_MAX=32/48:
ASan reports a stack-buffer-overflow on the 'tmp' array. Fixed to
MIN(len - off, sizeof(tmp) - n) and reverified clean at
RC_LINE_MAX=32/48/64/128.
The default config never hits this (the builtin preset is 95 bytes and
the default RC_LINE_MAX is 128), but SYSTEM_NXINIT_RC_LINE_MAX had no
lower bound, so lowering it towards 32/48 for a smaller build would
silently corrupt the stack while parsing the preset during boot. Add a
"range 64 4096" bound so the value can no longer be set below the
builtin preset's needs.
Assisted-by: opencode:mimo-v2.5-pro
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
init_parse_config_file() declares 'r' inside the read loop with no
blank line before the following 'if (r < 0)' statement, violating the
NuttX coding standard (nxstyle: "Missing blank line after
declarations"). checkpatch.sh runs a whole-file nxstyle check on any
file a commit touches, not diff-only, so this pre-existing issue
surfaced on this PR's CI once parser.c was touched again.
Assisted-by: GitHubCopilot:claude-sonnet-5
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
init_parse_config_lines() had a dead early "continue" for a truly
empty line (buf == "\0") that skipped the memmove() bookkeeping its
sibling whitespace-only-line branch performs. When a real empty line
appeared mid-buffer, subsequent bytes were never shifted to the front
of the working buffer, corrupting the remaining-length tracking and
silently dropping every line after it for that refill chunk.
The whitespace-skip loop right below already handles the empty-string
case correctly (the loop body never executes, so it falls straight
into the "only whitespace" -> memmove -> continue path), so the buggy
early exit is simply redundant and removed.
Assisted-by: GitHubCopilot:claude-sonnet-5
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
Previously, 'set KEY VALUE' in init.rc was not recognized as a builtin
command. It fell through to posix_spawnp(), which ran it in a
temporary child shell. The environment variable was set only in the
child process and lost when it exited, so services started afterward
never inherited it.
Register cmd_set as an init builtin that calls setenv(key, value, 1)
directly in the init process. The command takes exactly 2 arguments
(key and value). All code is guarded by CONFIG_DISABLE_ENVIRON so it
compiles out when environment support is disabled.
Since child processes inherit init's environment, 'set TZ Asia/Shanghai'
in init.rc now correctly propagates to all subsequently started
services.
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
The previous default of 8 is insufficient for services with many
arguments (e.g. ptpd needs 10 argv slots). When exceeded, argv lacks
a NULL terminator, causing posix_spawnp to read out of bounds.
Increase default to 16 to prevent argument truncation for typical
daemon services.
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
codespell flagged this in PR #3751 CI:
system/nxinit/init.c:119: unkown ==> unknown
system/nxinit/init.c:130: unkown ==> unknown
Both entries were introduced by the resetcause-for-triggers commit and
are unrelated to the earlier nxstyle regression already discussed on
the PR.
Assisted-by: GitHubCopilot:claude-sonnet-5
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
Add the missing mapping entries in the resetflag[] array to prevent
potential NULL pointer dereference when accessing reset.flag.
The resetflag array uses designated initializers and must have entries
for all BOARDIOC_SOFTRESETCAUSE_* values to avoid array holes.
Reported by: xuchuntian@xiaomi.com
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
Add built-in property sys.boot.reason, which allows action triggers to be
executed on specific reset cause.
For example:
```
on property:sys.boot.reason=cpu_soft_reset(bootloader)
echo "bootloader mode ..."
start fastboot
```
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
Move the boot event orchestration into a preset config buffer so the
"init" and (optional) "netinit"/"finalinit" events are triggered as a
serialized chain rather than queued back-to-back in main(). This fixes
the timing between preset event initialization and board initialization.
Adapted for the community tree: BOARDIOC_INIT has been removed upstream
(replaced by CONFIG_BOARD_LATE_INITIALIZE), so no board_init/board_finalinit
builtins are added and no boardctl(BOARDIOC_INIT)/boardctl(BOARDIOC_FINALINIT)
calls are reintroduced; board device init is now performed by the kernel
before init starts.
netinit is not a boardctl call, so it is kept in the serialized event
chain like the original: add a "netinit" builtin that calls
netinit_bringup(), driven by "on init -> trigger netinit -> on netinit",
instead of calling netinit_bringup() directly in main(). finalinit
remains a pure event for user-defined services to hook.
Assisted-by: GitHubCopilot:claude-opus-4.8
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
Add support for booting a new application firmware image.
Depends on `BOARDCTL_BOOT_IMAGE`.
When the built-in boot command of nsh is enabled, the built-in boot
command of Init will take precedence (see init_builtin_run()). To use
the built-in boot command of nsh, use: `exec -- sh boot [args...]`.
Referred to nshlib/nsh_syscmds.c: cmd_boot()
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
Add the parser[n].check validation loop to init_parse_config_buffer(),
matching the same pattern already used in init_parse_config_file().
This ensures that services and actions parsed from in-memory buffers
are properly validated after parsing.
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
The init_parse_arguments() function checked for '--' by only
comparing the first two characters, causing options like --system,
--nofork to be misinterpreted as the '--' argument separator. This
truncated the remaining arguments. Add an isblank() check on the
third character to ensure only standalone '--' followed by whitespace
triggers the separator logic.
Assisted-by: GitHubCopilot:claude-4.6-opus
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
On the basis of init.rc, add default parsing of cpu-specific configs.
- /etc/init.d/init.rc
- /etc/init.d/init.cpu${CPUID}.rc
Refactor function `init_parse_configs()` to parse files from the default path
instead of identifying and parsing directories or files, as the functionality
is unnecessary.
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
The `on <event>` action re-executed on every property poll because the
event pending flag was sticky: event_callback returned the same non-zero
pending value whether the event had just changed or had stayed satisfied
from an earlier change. init_action_foreach_event could not distinguish
an edge from a steady state and re-enqueued the action each round
(board_netinit ran 262 times per boot).
Introduce a three-state result (EVENT_STATE_UNSATISFIED / SATISFIED /
TRIGGERED). event_callback now returns TRIGGERED only on the edge where
pending flips false -> true. foreach folds per-event states into a
product clamped to TRIGGERED, enqueuing the action only when every event
is satisfied AND at least one fired this round.
Assisted-by: GitHubCopilot:claude-4.8-opus
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
Action triggered on any event before this fix (e.g. both opposite actions in
init.rc below triggered when event "boot" triggered).
init.rc
on boot && property:sys.boot.reason=bootloader
echo "On boot, the reason is BL."
on boot && property:sys.boot.reason!=bootloader
echo "On boot, the reason is not BL."
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
init.rc
on boot && property:sys.boot.reason!=bootloader
echo "On boot, the reason is not BL."
Before fixing
init_main: action 0x40436120
init_main: sys.boot.reason!=bootloader
init_main: argv[0] 'echo'
init_main: argv[1] 'On boot, the reason is not BL.'
After fixing
init_main: action 0x40436120
init_main: sys.boot.reason!=bootloader
+ init_main: default==boot
init_main: argv[0] 'echo'
init_main: argv[1] 'On boot, the reason is not BL.'
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
Previously only supported event trigger, now added support for
action triggers (property setting).
Steps to enable action triggers:
- Define all init_property_*() interfaces declared in this file.
- Data structures or functions that will likely be used:
- struct action_trigger_s
- init_action_for_every()
Example
```
on boot
setprop key_test
setprop key_test value_test /* property changed and matched */
trigger event_test
on event_test && property:key_test=value_test
echo "on event_test, property changed!"
on property:key_test=value_test
echo "property changed!"
```
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
Add the property backend and a setprop builtin so that setting a
property can feed action triggers. property_simple.c provides a minimal
init_property_*() implementation whose init_property_set() forwards the
key/value pair to init_action_trigger_event(), and init.c wires the
property poller into the init poll loop.
Signed-off-by: fangpeina <fangpeina@xiaomi.com>
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
Check the service argv array bound before reading the current entry in the debug dump loop. A full argument array may not have an in-array NULL terminator, so the old condition could read one entry past the array while CONFIG_SYSTEM_NXINIT_DEBUG is enabled.
Signed-off-by: Old-Ding <35417409+Old-Ding@users.noreply.github.com>
Fix argument parsing in init_parse_arguments() to properly handle
multiple quoted arguments like 'echo "arg1" "arg2"' by skipping
quote characters after processing them.
Signed-off-by: fangpeina <fangpeina@xiaomi.com>
The init process has blocked all signals, spawned services would
inherit that mask. This could cause services to miss important
signals like SIGTERM during graceful shutdown.
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
Pending all signals(SIGCHLD) when ppoll() is not invoked to
avoid race conditions.
Case reproduction
Set examples/hello as a service that exits immediately after startup.
```init.rc
on boot
start hello
service hello hello
restart_period 0
```
Log - without this patch:
# Service hello only restarts about 100 times, ppoll is not woken up
# after the hello process with PID 119 exits.
[ 4.391274] [ 2] [ 0] init_main: service 'hello' pid 118 exited status 0
[ 4.401423] [ 2] [ 0] init_main: started service 'hello' pid 119
Log - with this patch:
# ppoll() can still be woken up normally after tens of thousands of
# restarts of service hello in stress test.
[ 268.447747] [ 2] [ 0] init_main: service 'hello' pid 34503 exited status 0
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
Any string ending with whitespace passed to init_parse_arguments()
could cause the parser to advance past the string boundary and read
unintended memory content.
- " echo "A" \0& echo "B" should be parsed
as a command with two argvs instand of five.
- "command arg " may lead to uncertain results.
Signed-off-by: fangpeina <fangpeina@xiaomi.com>
reap_process() referenced an undeclared identifier 'wtatus' on
the WIFSIGNALED branch (typo of 'wstatus'). Some toolchains then
flagged a -Wmaybe-uninitialized on the surrounding wstatus use.
Correct the typo so WIFSIGNALED/WTERMSIG operate on the actual
wstatus value returned by waitpid().
Signed-off-by: fangpeina <fangpeina@xiaomi.com>
action.c uses clock_gettime(CLOCK_MONOTONIC, ...) but did not
pull in <nuttx/clock.h> directly, which fails to build on
configurations where the header is not transitively included.
Add the missing #include.
Signed-off-by: fangpeina <fangpeina@xiaomi.com>
When ETC_ROMFS is disabled to reduce the bin size, we can provide the init.rc
file via a pseudo-file in the boards/vendor directory. For example:
- CONFIG_ETC_ROMFS=n
- CONFIG_PSEUDOFS_FILE=y
- CONFIG_DISABLE_PSEUDOFS_OPERATIONS=n
```C
FAR const char *init_rc =
"on init\n"
" start console\n";
"service console sh\n"
" restart_period 100\n";
int fd = open("/etc/init.d/init.rc", O_WRONLY | O_CREAT);
/* ... */
ssize_t n = write(fd, init_rc, strlen(init_rc) + 1);
/* ... */
close(fd);
```
The last character '\0' in the file content will be treated as a new line,
and the number of parsed parameters will be zero (abnormal, there should be
at least one keyword).
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
Empty lines in init.rc caused parsing to fail with -EINVAL because init_parse_arguments() returns 0 for empty strings, triggering the 'argc < 1' error path in init_action_parse() and accessing uninitialized argv[0] in init_service_parse().
Fix by skipping empty lines and lines containing only whitespace before attempting to match section keywords or calling parser callbacks.
Fixesapache/nuttx-apps#3513
Signed-off-by: hanzj <hanzhijian@zepp.com>
Without debug enabled, the code would not compile due to checking
`WIFEXITED(wstatus)` when `wstatus` was uninitialized.
Signed-off-by: Matteo Golin <matteo.golin@gmail.com>
The init.rc file path is now configurable to allow users to choose where
to put the startup script. This is useful for devices that mount
external media to a special directory like `/sd`.
Signed-off-by: Matteo Golin <matteo.golin@gmail.com>
Would not compile due to typo in macro describing service name length.
Change ensures that:
* We do not malloc an extra `len` bytes since this is already allocated
as part of the struct
* The name string always has a null terminating byte
Signed-off-by: Matteo Golin <matteo.golin@gmail.com>
FIx