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>
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 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>
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>
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>
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>
This component (abbreviated as "NxInit") is specifically developed
for NuttX and intended for system initialization. While we have
adopted the Android Init Language among various syntax options,
this is a brand-new implementation—it is not a port or variant of
Android Init.
Refer to the implementation of Android Init Language, consists of five broad
classes of statements: Actions, Commands, Services, Options, and Imports.
Actions support two types of triggers: event and action. Action triggers also
support runtime triggering. Services support lifecycle management, including
automatic restart (at specified intervals), and starting/stopping
individually or by class. Import supports files or directories, and we may
add a static method in the future. The following are some differences:
1. The Android Init Language treats lines starting with `#` as comments,
while we use a preprocessor to handle comments.
2. For action commands, we can omit "exec" and directly execute
built-in apps or nsh builtins.
3. Regarding the property service, users can either adapt it by self or
directly use the preset NVS-based properties.
4. Only part of standard action commands and service options are
implemented currlently.
To enable system/nxinit:
```diff
-CONFIG_INIT_ENTRYPOINT="nsh_main"
+CONFIG_INIT_ENTRYPOINT="init_main"
+CONFIG_SYSTEM_NXINIT=y
```
For format and additional details, refer to:
https://android.googlesource.com/platform/system/core/+/
master/init/README.md
Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>